feat:merge features from 1.5.x-Hoxton.SR9. (#250)

pull/262/head
Haotian Zhang 2 years ago committed by GitHub
parent a656d9ca7f
commit ff25ca6975
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,4 +1,5 @@
# Change Log # Change Log
--- ---
- [Add metadata transfer example.](https://github.com/Tencent/spring-cloud-tencent/pull/211) - [Add metadata transfer example.](https://github.com/Tencent/spring-cloud-tencent/pull/210)
- [feat:merge features from 1.5.x-Hoxton.SR9.](https://github.com/Tencent/spring-cloud-tencent/pull/250)

@ -41,9 +41,6 @@ Copyright (c) guava authors and contributors.
6. reactor 6. reactor
Copyright (c) reactor authors and contributors. Copyright (c) reactor authors and contributors.
7. powermock
Copyright 2007-2017 PowerMock Contributors
Terms of the Apache v2.0 License: Terms of the Apache v2.0 License:
-------------------------------------------------------------------- --------------------------------------------------------------------

@ -1,81 +1,109 @@
# Spring Cloud Tencent # Spring Cloud Tencent
[![Wiki](https://badgen.net/badge/icon/wiki?icon=wiki&label)](https://github.com/Tencent/spring-cloud-tencent/wiki)
[![Build Status](https://github.com/Tencent/spring-cloud-tencent/actions/workflows/junit_test.yml/badge.svg)](https://github.com/Tencent/spring-cloud-tencent/actions/workflows/junit_test.yml)
[![Maven Central](https://img.shields.io/maven-central/v/com.tencent.cloud/spring-cloud-tencent?label=Maven%20Central)](https://search.maven.org/search?q=g:com.tencent.cloud%20AND%20a:spring-cloud-tencent)
[![codecov.io](https://codecov.io/gh/Tencent/spring-cloud-tencent/branch/main/graph/badge.svg)](https://codecov.io/gh/Tencent/spring-cloud-tencent?branch=main)
[![Contributors](https://img.shields.io/github/contributors/Tencent/spring-cloud-tencent)](https://github.com/Tencent/spring-cloud-tencent/graphs/contributors)
[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
[English](./README.md) | 简体中文 [English](./README.md) | 简体中文
--- ---
## 介绍 ## 介绍
Spring Cloud Tencent包含了分布式应用微服务开发过程中所需的组件基于 Spring Cloud 框架的开发者可以使用这些组件快速进行分布式应用的开发。 Spring Cloud Tencent 是腾讯开源的一站式微服务解决方案
## 主要功能 Spring Cloud Tencent 实现了Spring Cloud 标准微服务 SPI开发者可以基于 Spring Cloud Tencent 快速开发 Spring Cloud 云原生分布式应用。
* **服务注册与发现**:基于 Spring Cloud Common的标准进行微服务的注册与发现。 Spring Cloud Tencent 的核心依托腾讯开源的一站式服务发现与治理平台 [Polaris](https://github.com/polarismesh/polaris),实现各种分布式微服务场景。
* **服务路由与负载均衡**:基于 Ribbon 的接口标准,提供场景更丰富的动态路由以及负载均衡的能力。
* **故障节点熔断**:提供故障节点的熔断剔除以及主/被动探测恢复的能力,保证分布式服务的可靠性。
* **服务限流**:支持微服务被调接入层和网关主动调用的限流功能,保证后台微服务稳定性,可通过控制台动态配置规则,及查看流量监控数据。
* **元数据传递**: 支持网关及微服务应用之间的自定义元数据传递。
## 如何构建 - [Polaris Github home page](https://github.com/polarismesh/polaris)
- [Polaris official website](https://polarismesh.cn/)
* [2020.0.x](https://github.com/Tencent/spring-cloud-tencent/tree/2020.0.x)分支对应的是 Spring Cloud 2020.0版本编译环境最低支持JDK 1.8。 Spring Cloud Tencent提供的能力包括但不限于
* [main](https://github.com/Tencent/spring-cloud-tencent/tree/main) 分支对应的是 Spring Cloud Hoxton版本编译环境最低支持JDK 1.8。
* [greenwich](https://github.com/Tencent/spring-cloud-tencent/tree/greenwich) 分支对应的是 Spring Cloud Greenwich版本编译环境最低支持JDK 1.8。
Spring Cloud Tencent 使用 Maven 来构建,最快的使用方式是将本项目 clone 到本地,然后执行以下命令: <img width="1029" alt="image" src="https://user-images.githubusercontent.com/4991116/170412323-ecaf544c-1d7b-45db-9cf0-591544e50c64.png">
```bash
./mvnw install
```
执行完毕后,项目将被安装到本地 Maven 仓库。
## 如何使用 - 服务注册和发现
- 动态配置管理
- 服务治理
- 服务限流
- 服务熔断
- 服务路由
- ...
- 标签透传
### 如何引入依赖 ## 体验环境
在 dependencyManagement 中添加如下配置,然后在 dependencies 中添加自己所需使用的依赖即可使用。 - 管控台地址: http://14.116.241.63:8080/
- 账号polaris
- 密码polaris
- 控制面地址: `grpc://183.47.111.80:8091`
-
`spring-cloud-tencent-example` 下 example 地址都默认指向了体验服务地址(`grpc://183.47.111.80:8091`),如果您只是体验 Spring Cloud Tencent可直接一键运行任何 example。
## 管控台
```` <img width="1792" alt="image" src="https://user-images.githubusercontent.com/4991116/163402268-48493802-4555-4b93-8e31-011410f2166b.png">
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-dependencies</artifactId>
<version>1.1.4.Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
````
### 示例 ## 使用指南
Spring Cloud Tencent 项目包含了一个子模块spring-cloud-tencent-examples。此模块中提供了体验接入用的 example ,您可以阅读对应的 example 工程下的 readme 文档,根据里面的步骤来体验 Spring Cloud Tencent 所有组件都已上传到 Maven 中央仓库,只需要引入依赖即可。
Example 列表 例如:
- [PolarisMesh](https://github.com/polarismesh)接入相关的样例: ```` xml
<!-- add spring-cloud-tencent bom -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-dependencies</artifactId>
<!--version number-->
<version>${version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- [服务发现](spring-cloud-tencent-examples/polaris-discovery-example/README-zh.md) <!-- add spring-cloud-starter-tencent-polaris-discovery dependency -->
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
</dependencies>
- [故障熔断](spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md) ````
- [限流](spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md)
- [网关](spring-cloud-tencent-examples/polaris-gateway-example/README-zh.md) - ### 快速开始
- [Spring Cloud Tencent 版本管理](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86)
- [Spring Cloud Tencent 服务注册与发现](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Discovery-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 配置中心](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Config-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 限流](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Rate-Limit-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 熔断](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Circuitbreaker-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 服务路由](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Router-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 标签传递](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Metadata-Transfer-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
更多详细功能,请参考[polaris-java](https://github.com/polarismesh/polaris-java/blob/main/README-zh.md)。 - ### 开发文档
- [项目概览](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大版本号相关的版本策略。 扫描下面的二维码加入 Spring Cloud Tencent 交流群
项目的版本号格式为 ```大版本号.小版本号.补丁版本号-对应Spring Cloud的大版本号.对应Spring Cloud的小版本号-发布类型``` 的形式。 <img src="https://user-images.githubusercontent.com/24446200/169198148-d4cc3494-3485-4515-9897-c8cb5504f706.png" width="30%" height="30%" />
大版本号、小版本号、补丁版本号的类型为数字,从 0 开始取值。
对应Spring Cloud的大版本号为Spring Cloud提供的英文版本号例如Hoxton、Greenwich等。对应Spring Cloud的小版本号为Spring Cloud给出的小版本号例如 RS9 等。
发布类型目前包括正式发布和发布候选版RC。在实际的版本号中正式发布版不额外添加发布类型发布候选版将添加后缀并从 RC0 开始。
示例1.2.0-Hoxton.SR9-RC0
## License ## License
The spring-cloud-tencent is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE) The spring-cloud-tencent is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE)
## Stargazers over time
如果您对 Spring Cloud Tencent 有兴趣,请关注我们的项目~
[![Stargazers over time](https://starchart.cc/Tencent/spring-cloud-tencent.svg)](https://starchart.cc/Tencent/spring-cloud-tencent)

@ -1,89 +1,108 @@
# Spring Cloud Tencent # Spring Cloud Tencent
[![Wiki](https://badgen.net/badge/icon/wiki?icon=wiki&label)](https://github.com/Tencent/spring-cloud-tencent/wiki)
[![Build Status](https://github.com/Tencent/spring-cloud-tencent/actions/workflows/junit_test.yml/badge.svg)](https://github.com/Tencent/spring-cloud-tencent/actions/workflows/junit_test.yml) [![Build Status](https://github.com/Tencent/spring-cloud-tencent/actions/workflows/junit_test.yml/badge.svg)](https://github.com/Tencent/spring-cloud-tencent/actions/workflows/junit_test.yml)
[![Maven Central](https://img.shields.io/maven-central/v/com.tencent.cloud/spring-cloud-tencent?label=Maven%20Central)](https://search.maven.org/search?q=g:com.tencent.cloud%20AND%20a:spring-cloud-tencent) [![Maven Central](https://img.shields.io/maven-central/v/com.tencent.cloud/spring-cloud-tencent?label=Maven%20Central)](https://search.maven.org/search?q=g:com.tencent.cloud%20AND%20a:spring-cloud-tencent)
[![codecov.io](https://codecov.io/gh/Tencent/spring-cloud-tencent/branch/main/graph/badge.svg)](https://codecov.io/gh/Tencent/spring-cloud-tencent?branch=main)
[![Contributors](https://img.shields.io/github/contributors/Tencent/spring-cloud-tencent)](https://github.com/Tencent/spring-cloud-tencent/graphs/contributors)
[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
English | [简体中文](./README-zh.md) English | [简体中文](./README-zh.md)
## Introduction ## Introduction
Spring Cloud Tencent contains components distributed micro-service applications need during developing phase, developers that built their key architectures based on Spring Cloud can use these components Spring Cloud Tencent is a open source one-stop microservice solution from Tencent.
Based on Spring Cloud Tencent, you only need a small configuration to launch Spring Cloud and micro-service's joint solutions. Spring Cloud Tencent implements the Spring Cloud standard microservice SPI, so developers can quickly develop Spring Cloud cloud-native distributed applications based on Spring Cloud Tencent.
## Key Features The core of Spring Cloud Tencent relies on Tencent's open-source one-stop service discovery and governance platform [Polaris](https://github.com/polarismesh/polaris) to realize various distributed microservice scenarios.
* **Service Registration and Discovery**: Based on Spring Cloud's discovery and registration standard. - [Polaris Github home page](https://github.com/polarismesh/polaris)
* **Service Routing and LoadBalancer**: Based on ribbon's API port, provide dynamic routing and load balancing use cases. - [Polaris official website](https://polarismesh.cn/)
* **CircuitBreaker Node**: Support circuitbreaker auto-reset ability, ensure the reliability of distributed server
* **Rate Limiter**: Support rate limit of microservice and gateway, ensure the stability of backend, one can configure policies and traffic data from the control panel
* **Metadata Delivery**: Support metadata delivery between gateways and microservices.
## Components The capabilities provided by Spring Cloud Tencent include but are not limited to:
**[Polaris](https://github.com/PolarisMesh/polaris)**Polaris Spring Cloud operation centre, provide solutions to registration, dynamic routing, load balancing and circuitbreaker. <img width="1031" alt="image" src="https://user-images.githubusercontent.com/4991116/170412596-692f8dae-42f7-495f-a451-01396e381eb0.png">
## How to build - Service registration and discovery
- Dynamic configuration management
- Service Governance
- Service rate limit
- Service circuit breaker
- Service routing
- ...
- Label transparent transmission
* master's branch matches Spring Cloud Hoxton, support lowest at JDK 1.8. ## Demo Environment
Spring Cloud Tencent uses Maven to construct, the fastest way is to clone project to local files, then execute the following orders: - Console Address : http://14.116.241.63:8080/
- Username: polaris
- Password: polaris
- Server Address: `grpc://183.47.111.80:8091`
```bash The example addresses under `spring-cloud-tencent-example` all point to the experience service address (`grpc://183.47.111.80:8091`) by default.
./mvnw install If you only experience Spring Cloud Tencent, you can run any example directly with one click.
```
When all the steps are finished, the project will be installed in local Maven repository. ## Screenshots
## How to Use <img width="1792" alt="image" src="https://user-images.githubusercontent.com/4991116/163402268-48493802-4555-4b93-8e31-011410f2166b.png">
### How to Introduce Dependency ## Use Guide
Add the following configurations in dependencyManagement, then add the dependencies you need. All the components of Spring Cloud Tencent have been uploaded to the Maven central repository, just need to introduce dependencies.
At the same time, you need to pay attention to the Spring Cloud version corresponding to Spring Cloud Tencent, and then the corresponding Spring Boot version.
For example, Spring Cloud Tencent's 1.0.1.Hoxton.SR9 corresponds to the Spring Cloud Hoxton version and requires Spring Boot 2.3.x.
```` For example:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-dependencies</artifactId>
<!--version number-->
<version>${version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
````
### Example ```` xml
<!-- add spring-cloud-tencent bom -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-dependencies</artifactId>
<!--version number-->
<version>${version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Spring Cloud Tencent project contains a sub-module spring-cloud-tencent-examples. This module provides examples for users to experience, you can read the README.md in each example, and follow the instructions there. <!-- add spring-cloud-starter-tencent-polaris-discovery dependency -->
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
</dependencies>
Example List: ````
- [Polaris Discovery Example](spring-cloud-tencent-examples/polaris-discovery-example/README.md) - ### Quick Start
- [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 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)
- [Polaris CircuitBreaker Example](spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md) - ### Development Documentation
- [Project Structure Overview](https://github.com/Tencent/spring-cloud-tencent/wiki/%E9%A1%B9%E7%9B%AE%E6%A6%82%E8%A7%88)
- [Participate in co-construction](https://github.com/Tencent/spring-cloud-tencent/wiki/%E5%8F%82%E4%B8%8E%E5%85%B1%E5%BB%BA)
- [Polaris RateLimit Example](spring-cloud-tencent-examples/polaris-ratelimit-example/README.md) ## Chat Group
- [Polaris Gateway Example](spring-cloud-tencent-examples/polaris-gateway-example/README.md) Please scan the QR code to join the chat group.
For more features, please refer to [polaris-java](https://github.com/polarismesh/polaris-java). <img src="https://user-images.githubusercontent.com/24446200/169198148-d4cc3494-3485-4515-9897-c8cb5504f706.png" width="30%" height="30%" />
### Version Standard ## License
The spring-cloud-tencent is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE)
We use a version policy related to Spring Cloud's major version number.
Project version includes ```${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}-${CORRESPONDING_MAJOR_VERSION_OF_SPRING_CLOUD}.${CORRESPONDING_MINOR_VERSION_OF_SPRING_CLOUD}-${RELEASE_TYPE}```. ## Stargazers over time
```${MAJOR_VERSION}```, ```${MINOR_VERSION}```, ```${PATCH_VERSION}``` are in numbers starting from 0.
```${CORRESPONDING_MAJOR_VERSION_OF_SPRING_CLOUD}``` is the same as the major version number of Spring Cloud, like Hoxton, Greenwich. ```${CORRESPONDING_MINOR_VERSION_OF_SPRING_CLOUD}``` is the same as the major version number of Spring Cloud, like RS9.
```${RELEASE_TYPE}``` is like RELEASE or RC currently. Actually, the RELEASE version does not add a release type in the version, and the RS version will add a suffix and start from RC0.
For example: 1.2.0-Hoxton.SR9-RC0 If you are interested in Spring Cloud Tencent, please follow our project, thank you very much.
## License [![Stargazers over time](https://starchart.cc/Tencent/spring-cloud-tencent.svg)](https://starchart.cc/Tencent/spring-cloud-tencent)
The spring-cloud-tencent is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE)

@ -86,14 +86,11 @@
<properties> <properties>
<!-- Project revision --> <!-- Project revision -->
<revision>1.5.0-2021.0.2-SNAPSHOT</revision> <revision>1.5.2-2021.0.2-SNAPSHOT</revision>
<!-- Spring Cloud --> <!-- Spring Cloud -->
<spring.cloud.version>2021.0.2</spring.cloud.version> <spring.cloud.version>2021.0.2</spring.cloud.version>
<!-- Dependencies -->
<logback.version>1.2.11</logback.version>
<!-- Maven Plugin Versions --> <!-- Maven Plugin Versions -->
<jacoco.version>0.8.3</jacoco.version> <jacoco.version>0.8.3</jacoco.version>
<maven-source-plugin.version>3.2.0</maven-source-plugin.version> <maven-source-plugin.version>3.2.0</maven-source-plugin.version>
@ -109,15 +106,6 @@
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<!-- Spring Cloud Dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Tencent Dependencies --> <!-- Spring Cloud Tencent Dependencies -->
<dependency> <dependency>
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
@ -127,10 +115,13 @@
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- Spring Cloud Dependencies -->
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>logback-classic</artifactId> <artifactId>spring-cloud-dependencies</artifactId>
<version>${logback.version}</version> <version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

@ -0,0 +1,80 @@
/*
* 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.core;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
/**
* resolve custom transitive metadata from request.
*@author lepdou 2022-05-20
*/
public class CustomTransitiveMetadataResolver {
private static final String TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-";
private static final int TRANSITIVE_HEADER_PREFIX_LENGTH = TRANSITIVE_HEADER_PREFIX.length();
public static Map<String, String> resolve(ServerWebExchange exchange) {
Map<String, String> result = new HashMap<>();
HttpHeaders headers = exchange.getRequest().getHeaders();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String key = entry.getKey();
if (StringUtils.isNotBlank(key) &&
StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
&& !CollectionUtils.isEmpty(entry.getValue())) {
String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, entry.getValue().get(0));
}
}
return result;
}
public static Map<String, String> resolve(HttpServletRequest request) {
Map<String, String> result = new HashMap<>();
Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
String key = headers.nextElement();
if (StringUtils.isNotBlank(key) &&
StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
&& StringUtils.isNotBlank(request.getHeader(key))) {
String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, request.getHeader(key));
}
}
return result;
}
}

@ -20,6 +20,7 @@ package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
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;
@ -56,8 +57,31 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) { public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
// 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> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(serverWebExchange);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
MetadataContextHolder.init(mergedTransitiveMetadata);
// Save to ServerWebExchange.
serverWebExchange.getAttributes().put(
MetadataConstant.HeaderName.METADATA_CONTEXT,
MetadataContextHolder.get());
return webFilterChain.filter(serverWebExchange)
.doOnError(throwable -> LOG.error("handle metadata[{}] error.",
MetadataContextHolder.get(), throwable))
.doFinally((type) -> MetadataContextHolder.remove());
}
private Map<String, String> getIntervalTransitiveMetadata(ServerHttpRequest serverHttpRequest) {
HttpHeaders httpHeaders = serverHttpRequest.getHeaders(); HttpHeaders httpHeaders = serverHttpRequest.getHeaders();
String customMetadataStr = httpHeaders.getFirst(MetadataConstant.HeaderName.CUSTOM_METADATA); String customMetadataStr = httpHeaders
.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");
@ -68,17 +92,7 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
} }
LOG.debug("Get upstream metadata string: {}", customMetadataStr); LOG.debug("Get upstream metadata string: {}", customMetadataStr);
// create custom metadata. return JacksonUtils.deserialize2Map(customMetadataStr);
Map<String, String> upstreamCustomMetadataMap = JacksonUtils.deserialize2Map(customMetadataStr);
MetadataContextHolder.init(upstreamCustomMetadataMap);
// Save to ServerWebExchange.
serverWebExchange.getAttributes().put(MetadataConstant.HeaderName.METADATA_CONTEXT,
MetadataContextHolder.get());
return webFilterChain.filter(serverWebExchange)
.doOnError(throwable -> LOG.error("handle metadata[{}] error.", MetadataContextHolder.get(), throwable))
.doFinally((type) -> MetadataContextHolder.remove());
} }
} }

@ -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.URLDecoder; import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
@ -50,8 +51,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, HttpServletResponse httpServletResponse, protected void doFilterInternal(HttpServletRequest httpServletRequest,
FilterChain filterChain) throws ServletException, IOException { HttpServletResponse httpServletResponse, FilterChain filterChain)
throws ServletException, IOException {
Map<String, String> internalTransitiveMetadata = getInternalTransitiveMetadata(httpServletRequest);
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(httpServletRequest);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
try {
MetadataContextHolder.init(mergedTransitiveMetadata);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
catch (IOException | ServletException | RuntimeException e) {
throw e;
}
}
private Map<String, String> getInternalTransitiveMetadata(HttpServletRequest httpServletRequest) {
// 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(MetadataConstant.HeaderName.CUSTOM_METADATA);
try { try {
@ -65,19 +85,7 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
LOG.debug("Get upstream metadata string: {}", customMetadataStr); LOG.debug("Get upstream metadata string: {}", customMetadataStr);
// create custom metadata. // create custom metadata.
Map<String, String> upstreamCustomMetadataMap = JacksonUtils.deserialize2Map(customMetadataStr); return JacksonUtils.deserialize2Map(customMetadataStr);
try {
MetadataContextHolder.init(upstreamCustomMetadataMap);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
catch (IOException | ServletException | RuntimeException e) {
throw e;
}
finally {
MetadataContextHolder.remove();
}
} }
} }

@ -19,7 +19,6 @@
package com.tencent.cloud.metadata.core; package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.Map; import java.util.Map;
@ -56,39 +55,17 @@ public class EncodeTransferMedataFeignInterceptor implements RequestInterceptor,
public void apply(RequestTemplate requestTemplate) { public void apply(RequestTemplate requestTemplate) {
// 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);
// add new metadata and cover old
if (!CollectionUtils.isEmpty(requestTemplate.headers())
&& !CollectionUtils.isEmpty(requestTemplate.headers().get(CUSTOM_METADATA))) {
for (String headerMetadataStr : requestTemplate.headers().get(CUSTOM_METADATA)) {
try {
// Since feign-core 11.1, RequestTemplate Adapted to RFC6570, See: https://tools.ietf.org/html/rfc6570#section-2.2
// Feign ISSUES : https://github.com/OpenFeign/feign/pull/1203
// Feign ISSUES : https://github.com/OpenFeign/feign/issues/1305
// Fixed : If you used RequestTemplate#header(name,value) method , the value needs to be encoded using URLEncoder .
headerMetadataStr = URLDecoder.decode(headerMetadataStr, "UTF-8");
Map<String, String> headerMetadataMap = JacksonUtils.deserialize2Map(headerMetadataStr);
for (String key : headerMetadataMap.keySet()) {
metadataContext.putTransitiveCustomMetadata(key, headerMetadataMap.get(key));
}
}
catch (UnsupportedEncodingException e) {
LOG.error("Set header failed.", e);
throw new RuntimeException(e);
}
}
}
Map<String, String> customMetadata = metadataContext.getAllTransitiveCustomMetadata();
if (!CollectionUtils.isEmpty(customMetadata)) { if (!CollectionUtils.isEmpty(customMetadata)) {
String metadataStr = JacksonUtils.serialize2Json(customMetadata); String encodedTransitiveMetadata = JacksonUtils.serialize2Json(customMetadata);
requestTemplate.removeHeader(CUSTOM_METADATA); requestTemplate.removeHeader(CUSTOM_METADATA);
try { try {
requestTemplate.header(CUSTOM_METADATA, URLEncoder.encode(metadataStr, "UTF-8")); requestTemplate.header(CUSTOM_METADATA, 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, metadataStr); requestTemplate.header(CUSTOM_METADATA, encodedTransitiveMetadata);
} }
} }
} }

@ -27,7 +27,6 @@ import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.http.HttpRequest; import org.springframework.http.HttpRequest;
@ -54,24 +53,16 @@ public class EncodeTransferMedataRestTemplateInterceptor implements ClientHttpRe
ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { 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);
// add new metadata and cover old
String metadataStr = httpRequest.getHeaders().getFirst(MetadataConstant.HeaderName.CUSTOM_METADATA);
if (StringUtils.isNotBlank(metadataStr)) {
Map<String, String> headerMetadataMap = JacksonUtils.deserialize2Map(metadataStr);
for (String key : headerMetadataMap.keySet()) {
metadataContext.putTransitiveCustomMetadata(key, headerMetadataMap.get(key));
}
}
Map<String, String> customMetadata = metadataContext.getAllTransitiveCustomMetadata();
if (!CollectionUtils.isEmpty(customMetadata)) { if (!CollectionUtils.isEmpty(customMetadata)) {
metadataStr = JacksonUtils.serialize2Json(customMetadata); String encodedTransitiveMetadata = JacksonUtils.serialize2Json(customMetadata);
try { try {
httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA, httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA,
URLEncoder.encode(metadataStr, "UTF-8")); URLEncoder.encode(encodedTransitiveMetadata, "UTF-8"));
} }
catch (UnsupportedEncodingException e) { catch (UnsupportedEncodingException e) {
httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA, metadataStr); httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA, encodedTransitiveMetadata);
} }
} }
return clientHttpRequestExecution.execute(httpRequest, bytes); return clientHttpRequestExecution.execute(httpRequest, bytes);

@ -63,7 +63,7 @@ public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered {
if (metadataContext == null) { if (metadataContext == null) {
metadataContext = MetadataContextHolder.get(); metadataContext = MetadataContextHolder.get();
} }
Map<String, String> customMetadata = metadataContext.getAllTransitiveCustomMetadata(); Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
if (!CollectionUtils.isEmpty(customMetadata)) { if (!CollectionUtils.isEmpty(customMetadata)) {
String metadataStr = JacksonUtils.serialize2Json(customMetadata); String metadataStr = JacksonUtils.serialize2Json(customMetadata);
try { try {

@ -41,7 +41,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = MOCK, classes = DecodeTransferMetadataServletFilterTest.TestApplication.class, @SpringBootTest(webEnvironment = MOCK,
classes = DecodeTransferMetadataServletFilterTest.TestApplication.class,
properties = { "spring.config.location = classpath:application-test.yml" }) properties = { "spring.config.location = classpath:application-test.yml" })
public class DecodeTransferMetadataReactiveFilterTest { public class DecodeTransferMetadataReactiveFilterTest {
@ -68,12 +69,15 @@ public class DecodeTransferMetadataReactiveFilterTest {
// Mock request // Mock request
MockServerHttpRequest request = MockServerHttpRequest.get("test") MockServerHttpRequest request = MockServerHttpRequest.get("test")
.header(MetadataConstant.HeaderName.CUSTOM_METADATA, "{\"c\": \"3\"}").build(); .header(MetadataConstant.HeaderName.CUSTOM_METADATA, "{\"c\": \"3\"}")
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request); ServerWebExchange exchange = MockServerWebExchange.from(request);
metadataReactiveFilter.filter(exchange, webFilterChain); metadataReactiveFilter.filter(exchange, webFilterChain);
Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1"); Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2"); .isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2");
Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull(); Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull();
} }

@ -43,7 +43,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT, classes = DecodeTransferMetadataServletFilterTest.TestApplication.class, @SpringBootTest(webEnvironment = RANDOM_PORT,
classes = DecodeTransferMetadataServletFilterTest.TestApplication.class,
properties = { "spring.config.location = classpath:application-test.yml" }) properties = { "spring.config.location = classpath:application-test.yml" })
public class DecodeTransferMetadataServletFilterTest { public class DecodeTransferMetadataServletFilterTest {
@ -65,8 +66,10 @@ public class DecodeTransferMetadataServletFilterTest {
request.addHeader(MetadataConstant.HeaderName.CUSTOM_METADATA, "{\"c\": \"3\"}"); request.addHeader(MetadataConstant.HeaderName.CUSTOM_METADATA, "{\"c\": \"3\"}");
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
metadataServletFilter.doFilter(request, response, filterChain); metadataServletFilter.doFilter(request, response, filterChain);
Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1"); Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2"); .isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2");
Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull(); Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull();
} }

@ -28,7 +28,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.GlobalFilter;
/** /**
* Test for {@link MetadataTransferAutoConfiguration} * Test for {@link MetadataTransferAutoConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -20,11 +20,8 @@ package com.tencent.cloud.metadata.core.intercepter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.stream.Collectors;
import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor; import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor;
import feign.RequestInterceptor; import feign.RequestInterceptor;
@ -33,17 +30,12 @@ import org.assertj.core.api.Assertions;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -59,8 +51,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = DEFINED_PORT, @SpringBootTest(webEnvironment = DEFINED_PORT,
classes = EncodeTransferMedataFeignInterceptorTest.TestApplication.class, classes = EncodeTransferMedataFeignInterceptorTest.TestApplication.class,
properties = { "server.port=8081", properties = {"server.port=8081", "spring.config.location = classpath:application-test.yml"})
"spring.config.location = classpath:application-test.yml" })
public class EncodeTransferMedataFeignInterceptorTest { public class EncodeTransferMedataFeignInterceptorTest {
@Autowired @Autowired
@ -72,21 +63,9 @@ public class EncodeTransferMedataFeignInterceptorTest {
@Test @Test
public void test1() { public void test1() {
String metadata = testFeign.test(); String metadata = testFeign.test();
Assertions.assertThat(metadata) Assertions.assertThat(metadata).isEqualTo("{\"b\":\"2\"}");
.isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}"); Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("a")) Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2");
.isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2");
Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("a"))
.isEqualTo("11");
Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("b"))
.isEqualTo("22");
Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("c"))
.isEqualTo("33");
} }
@SpringBootApplication @SpringBootApplication
@ -101,18 +80,13 @@ public class EncodeTransferMedataFeignInterceptorTest {
return URLDecoder.decode(customMetadataStr, "UTF-8"); return URLDecoder.decode(customMetadataStr, "UTF-8");
} }
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
@FeignClient(name = "test-feign", url = "http://localhost:8081") @FeignClient(name = "test-feign", url = "http://localhost:8081")
public interface TestFeign { public interface TestFeign {
@RequestMapping(value = "/test", @RequestMapping(value = "/test",
headers = { MetadataConstant.HeaderName.CUSTOM_METADATA headers = {"X-SCT-Metadata-Transitive-a=11",
+ "={\"a\":\"11" + "\",\"b\":\"22\",\"c\":\"33\"}" }) "X-SCT-Metadata-Transitive-b=22",
"X-SCT-Metadata-Transitive-c=33"})
String test(); String test();
} }
@ -122,17 +96,8 @@ public class EncodeTransferMedataFeignInterceptorTest {
@Override @Override
public void apply(RequestTemplate template) { public void apply(RequestTemplate template) {
try { template.header(MetadataConstant.HeaderName.CUSTOM_METADATA,
// Since feign-core 11.1, RequestTemplate Adapted to RFC6570, See: https://tools.ietf.org/html/rfc6570#section-2.2 "{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}");
// Feign ISSUES : https://github.com/OpenFeign/feign/pull/1203
// Feign ISSUES : https://github.com/OpenFeign/feign/issues/1305
// Fixed : If you used RequestTemplate#header(name,value) method , the value needs to be encoded using URLEncoder .
template.header(MetadataConstant.HeaderName.CUSTOM_METADATA,
URLEncoder.encode("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}", "UTF-8"));
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
} }
} }

@ -22,10 +22,8 @@ import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor; import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor;
import org.assertj.core.api.Assertions;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -34,9 +32,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -67,29 +62,29 @@ public class EncodeTransferMedataRestTemplateInterceptorTest {
@Test @Test
public void test1() { public void test1() {
HttpHeaders httpHeaders = new HttpHeaders(); // HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set(MetadataConstant.HeaderName.CUSTOM_METADATA, // httpHeaders.set(MetadataConstant.HeaderName.CUSTOM_METADATA,
"{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}"); // "{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}");
HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders); // HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders);
String metadata = restTemplate // String metadata = restTemplate
.exchange("http://localhost:" + localServerPort + "/test", HttpMethod.GET, // .exchange("http://localhost:" + localServerPort + "/test", HttpMethod.GET,
httpEntity, String.class) // httpEntity, String.class)
.getBody(); // .getBody();
Assertions.assertThat(metadata) // Assertions.assertThat(metadata)
.isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}"); // .isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}");
Assertions.assertThat(metadataLocalProperties.getContent().get("a")) // Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
.isEqualTo("1"); // .isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b")) // Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2"); // .isEqualTo("2");
Assertions // Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("a")) // .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "a"))
.isEqualTo("11"); // .isEqualTo("11");
Assertions // Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("b")) // .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "b"))
.isEqualTo("22"); // .isEqualTo("22");
Assertions // Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("c")) // .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "c"))
.isEqualTo("33"); // .isEqualTo("33");
} }
@SpringBootApplication @SpringBootApplication

@ -95,5 +95,11 @@
<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>

@ -13,7 +13,6 @@
* 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.polaris.circuitbreaker;

@ -30,6 +30,8 @@ import feign.Request;
import feign.Request.Options; import feign.Request.Options;
import feign.Response; 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; import static feign.Util.checkNotNull;
@ -40,6 +42,9 @@ import static feign.Util.checkNotNull;
*/ */
public class PolarisFeignClient implements Client { public class PolarisFeignClient implements Client {
private static final Logger LOG = LoggerFactory.getLogger(PolarisFeignClient.class);
private final Client delegate; private final Client delegate;
private final ConsumerAPI consumerAPI; private final ConsumerAPI consumerAPI;
@ -58,10 +63,13 @@ public class PolarisFeignClient implements Client {
if (response.status() >= 500) { if (response.status() >= 500) {
resultRequest.setRetStatus(RetStatus.RetFail); resultRequest.setRetStatus(RetStatus.RetFail);
} }
LOG.debug("Will report result of {}. Request=[{}]. Response=[{}].",
resultRequest.getRetStatus().name(), request, response);
return response; return response;
} }
catch (IOException origin) { catch (IOException origin) {
resultRequest.setRetStatus(RetStatus.RetFail); resultRequest.setRetStatus(RetStatus.RetFail);
LOG.debug("Will report result of {}. Request=[{}].", resultRequest.getRetStatus().name(), request, origin);
throw origin; throw origin;
} }
finally { finally {

@ -13,45 +13,32 @@
* 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; package com.tencent.cloud.polaris.circuitbreaker;
import org.junit.Test; import org.junit.Test;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; import org.springframework.boot.autoconfigure.AutoConfigurations;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
/** /**
* Test for {@link PolarisDiscoveryProperties} * Test for {@link PolarisCircuitBreakerBootstrapConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
public class PolarisPropertiesTest { public class PolarisCircuitBreakerBootstrapConfigurationTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(PolarisCircuitBreakerBootstrapConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@Test @Test
public void testInitAndGetSet() { public void testDefaultInitialization() {
PolarisDiscoveryProperties temp = new PolarisDiscoveryProperties(); this.contextRunner.run(context -> {
try { assertThat(context).hasSingleBean(PolarisCircuitBreakerBootstrapConfiguration.CircuitBreakerConfigModifier.class);
temp.setNamespace(NAMESPACE_TEST); });
assertThat(temp.getNamespace()).isEqualTo(NAMESPACE_TEST);
temp.setService(SERVICE_PROVIDER);
assertThat(temp.getService()).isEqualTo(SERVICE_PROVIDER);
temp.setToken("xxxxxx");
assertThat(temp.getToken()).isEqualTo("xxxxxx");
temp.init();
assertThat(temp).isNotNull();
}
catch (Exception e) {
fail();
e.printStackTrace();
}
} }
} }

@ -0,0 +1,52 @@
/*
* 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.circuitbreaker;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignBeanPostProcessor;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import com.tencent.polaris.api.core.ConsumerAPI;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisFeignClientAutoConfiguration}.
*
* @author Haotian Zhang
*/
public class PolarisFeignClientAutoConfigurationTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(
PolarisContextAutoConfiguration.class,
PolarisFeignClientAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(ConsumerAPI.class);
assertThat(context).hasSingleBean(PolarisFeignBeanPostProcessor.class);
});
}
}

@ -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.circuitbreaker.feign;
import com.tencent.polaris.api.core.ConsumerAPI;
import feign.Client;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
/**
* Test for {@link PolarisFeignBeanPostProcessor}.
*
* @author Haotian Zhang
*/
public class PolarisFeignBeanPostProcessorTest {
private PolarisFeignBeanPostProcessor polarisFeignBeanPostProcessor;
@Before
public void setUp() {
ConsumerAPI consumerAPI = mock(ConsumerAPI.class);
polarisFeignBeanPostProcessor = new PolarisFeignBeanPostProcessor(consumerAPI);
}
@Test
public void testPostProcessBeforeInitialization() {
BeanFactory beanFactory = mock(BeanFactory.class);
doAnswer(invocation -> {
Class<?> clazz = invocation.getArgument(0);
if (clazz.equals(BlockingLoadBalancerClient.class)) {
return mock(BlockingLoadBalancerClient.class);
}
if (clazz.equals(LoadBalancerProperties.class)) {
return mock(LoadBalancerProperties.class);
}
if (clazz.equals(LoadBalancerClientFactory.class)) {
return mock(LoadBalancerClientFactory.class);
}
return null;
}).when(beanFactory).getBean(any(Class.class));
polarisFeignBeanPostProcessor.setBeanFactory(beanFactory);
// isNeedWrap(bean) == false
Object bean1 = new Object();
Object bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean1, "bean1");
assertThat(bean).isNotInstanceOfAny(
PolarisFeignClient.class,
PolarisFeignBlockingLoadBalancerClient.class);
// bean instanceOf Client.class
Client bean2 = mock(Client.class);
bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean2, "bean2");
assertThat(bean).isInstanceOf(PolarisFeignClient.class);
// bean instanceOf FeignBlockingLoadBalancerClient.class
FeignBlockingLoadBalancerClient bean3 = mock(FeignBlockingLoadBalancerClient.class);
doReturn(mock(Client.class)).when(bean3).getDelegate();
bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean3, "bean3");
assertThat(bean).isInstanceOf(PolarisFeignBlockingLoadBalancerClient.class);
}
}

@ -0,0 +1,39 @@
/*
* 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.circuitbreaker.feign;
import org.assertj.core.api.Assertions;
import org.junit.Test;
/**
* Test for {@link PolarisFeignBlockingLoadBalancerClient}.
*
* @author Haotian Zhang
*/
public class PolarisFeignBlockingLoadBalancerClientTest {
@Test
public void testConstructor() {
try {
new PolarisFeignBlockingLoadBalancerClient(null, null, null, null);
}
catch (Exception e) {
Assertions.fail("Exception encountered.", e);
}
}
}

@ -17,48 +17,121 @@
package com.tencent.cloud.polaris.circuitbreaker.feign; package com.tencent.cloud.polaris.circuitbreaker.feign;
import com.tencent.cloud.polaris.circuitbreaker.PolarisFeignClientAutoConfiguration; import java.io.IOException;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import com.google.common.collect.Maps;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import feign.Client; import feign.Client;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
import feign.Target;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith; 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.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
/** /**
* Test for {@link PolarisFeignClient}. * Test for {@link PolarisFeignClient}.
* *
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a> * @author Haotian Zhang
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(classes = TestPolarisFeignApp.class) @SpringBootTest(classes = PolarisFeignClientTest.TestApplication.class,
@ContextConfiguration(classes = { PolarisFeignClientAutoConfiguration.class, PolarisContextAutoConfiguration.class }) properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"})
public class PolarisFeignClientTest { public class PolarisFeignClientTest {
@Autowired
private ApplicationContext springCtx;
@Test @Test
public void testPolarisFeignBeanPostProcessor() { public void testConstructor() {
final PolarisFeignBeanPostProcessor postProcessor = springCtx.getBean(PolarisFeignBeanPostProcessor.class); try {
Assertions.assertNotNull(postProcessor, "PolarisFeignBeanPostProcessor"); new PolarisFeignClient(null, null);
fail("NullPointerException should be thrown.");
}
catch (Throwable e) {
assertThat(e).isInstanceOf(NullPointerException.class);
assertThat(e.getMessage()).isEqualTo("target");
}
try {
new PolarisFeignClient(mock(Client.class), null);
fail("NullPointerException should be thrown.");
}
catch (Throwable e) {
assertThat(e).isInstanceOf(NullPointerException.class);
assertThat(e.getMessage()).isEqualTo("CircuitBreakAPI");
}
try {
assertThat(new PolarisFeignClient(mock(Client.class), mock(ConsumerAPI.class))).isInstanceOf(PolarisFeignClient.class);
}
catch (Throwable e) {
fail("Exception encountered.", e);
}
} }
@Test @Test
public void testFeignClient() { public void testExecute() throws IOException {
final Client client = springCtx.getBean(Client.class); // mock Client.class
if (client instanceof PolarisFeignClient) { Client delegate = mock(Client.class);
return; doAnswer(invocation -> {
Request request = invocation.getArgument(0);
if (request.httpMethod().equals(Request.HttpMethod.GET)) {
return Response.builder().request(request).status(200).build();
}
else if (request.httpMethod().equals(Request.HttpMethod.POST)) {
return Response.builder().request(request).status(500).build();
}
throw new IOException("Mock exception.");
}).when(delegate).execute(any(Request.class), nullable(Request.Options.class));
// mock ConsumerAPI.class
ConsumerAPI consumerAPI = mock(ConsumerAPI.class);
doNothing().when(consumerAPI).updateServiceCallResult(any(ServiceCallResult.class));
// mock target
Target<Object> target = Target.EmptyTarget.create(Object.class);
// mock RequestTemplate.class
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.feignTarget(target);
PolarisFeignClient polarisFeignClient = new PolarisFeignClient(delegate, consumerAPI);
// 200
Response response = polarisFeignClient.execute(Request.create(Request.HttpMethod.GET, "http://localhost:8080/test",
Maps.newHashMap(), null, requestTemplate), null);
assertThat(response.status()).isEqualTo(200);
// 200
response = polarisFeignClient.execute(Request.create(Request.HttpMethod.POST, "http://localhost:8080/test",
Maps.newHashMap(), null, requestTemplate), null);
assertThat(response.status()).isEqualTo(500);
// Exception
try {
polarisFeignClient.execute(Request.create(Request.HttpMethod.DELETE, "http://localhost:8080/test",
Maps.newHashMap(), null, requestTemplate), null);
fail("IOException should be thrown.");
} }
if (client instanceof PolarisFeignBlockingLoadBalancerClient) { catch (Throwable t) {
return; assertThat(t).isInstanceOf(IOException.class);
assertThat(t.getMessage()).isEqualTo("Mock exception.");
} }
throw new IllegalStateException("Polaris burying failed");
} }
@SpringBootApplication
protected static class TestApplication {
}
} }

@ -31,6 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
/** /**
* polaris config module auto configuration at bootstrap phase. * polaris config module auto configuration at bootstrap phase.
@ -61,9 +62,9 @@ public class PolarisConfigBootstrapAutoConfiguration {
@Bean @Bean
public PolarisConfigFileLocator polarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties, public PolarisConfigFileLocator polarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties,
PolarisContextProperties polarisContextProperties, ConfigFileService configFileService, PolarisContextProperties polarisContextProperties, ConfigFileService configFileService,
PolarisPropertySourceManager polarisPropertySourceManager) { PolarisPropertySourceManager polarisPropertySourceManager, Environment environment) {
return new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, configFileService, return new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, configFileService,
polarisPropertySourceManager); polarisPropertySourceManager, environment);
} }
@Bean @Bean

@ -1,23 +1,23 @@
/* /*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available. * 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. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the BSD 3-Clause License (the "License"); * Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* https://opensource.org/licenses/BSD-3-Clause * 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.
* *
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/ */
package com.tencent.cloud.polaris.config.adapter; package com.tencent.cloud.polaris.config.adapter;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -26,8 +26,10 @@ import com.tencent.cloud.polaris.config.config.ConfigFileGroup;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.enums.ConfigFileFormat; import com.tencent.cloud.polaris.config.enums.ConfigFileFormat;
import com.tencent.cloud.polaris.context.PolarisContextProperties; import com.tencent.cloud.polaris.context.PolarisContextProperties;
import com.tencent.polaris.configuration.api.core.ConfigFileMetadata;
import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.api.core.ConfigFileService;
import com.tencent.polaris.configuration.api.core.ConfigKVFile; import com.tencent.polaris.configuration.api.core.ConfigKVFile;
import com.tencent.polaris.configuration.client.internal.DefaultConfigFileMetadata;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -61,13 +63,16 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
private final PolarisPropertySourceManager polarisPropertySourceManager; private final PolarisPropertySourceManager polarisPropertySourceManager;
private final Environment environment;
public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties, public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties,
PolarisContextProperties polarisContextProperties, ConfigFileService configFileService, PolarisContextProperties polarisContextProperties, ConfigFileService configFileService,
PolarisPropertySourceManager polarisPropertySourceManager) { PolarisPropertySourceManager polarisPropertySourceManager, Environment environment) {
this.polarisConfigProperties = polarisConfigProperties; this.polarisConfigProperties = polarisConfigProperties;
this.polarisContextProperties = polarisContextProperties; this.polarisContextProperties = polarisContextProperties;
this.configFileService = configFileService; this.configFileService = configFileService;
this.polarisPropertySourceManager = polarisPropertySourceManager; this.polarisPropertySourceManager = polarisPropertySourceManager;
this.environment = environment;
} }
@Override @Override
@ -80,19 +85,77 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
return compositePropertySource; return compositePropertySource;
} }
initPolarisConfigFiles(compositePropertySource, configFileGroups); initInternalConfigFiles(compositePropertySource);
initCustomPolarisConfigFiles(compositePropertySource, configFileGroups);
return compositePropertySource; return compositePropertySource;
} }
private void initPolarisConfigFiles(CompositePropertySource compositePropertySource, private void initInternalConfigFiles(CompositePropertySource compositePropertySource) {
List<ConfigFileMetadata> internalConfigFiles = getInternalConfigFiles();
for (ConfigFileMetadata configFile : internalConfigFiles) {
PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(
configFile.getNamespace(), configFile.getFileGroup(), configFile.getFileName());
compositePropertySource.addPropertySource(polarisPropertySource);
polarisPropertySourceManager.addPropertySource(polarisPropertySource);
LOGGER.info("[SCT Config] Load and inject polaris config file. file = {}", configFile);
}
}
private List<ConfigFileMetadata> getInternalConfigFiles() {
String namespace = polarisContextProperties.getNamespace();
String serviceName = polarisContextProperties.getService();
if (!StringUtils.hasText(serviceName)) {
serviceName = environment.getProperty("spring.application.name");
}
List<ConfigFileMetadata> internalConfigFiles = new LinkedList<>();
// priority: application-${profile} > application > boostrap-${profile} > boostrap
String[] activeProfiles = environment.getActiveProfiles();
for (String activeProfile : activeProfiles) {
if (!StringUtils.hasText(activeProfile)) {
continue;
}
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application-" + activeProfile + ".properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application-" + activeProfile + ".yml"));
}
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application.properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application.yml"));
for (String activeProfile : activeProfiles) {
if (!StringUtils.hasText(activeProfile)) {
continue;
}
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap-" + activeProfile + ".properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap-" + activeProfile + ".yml"));
}
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap.properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap.yml"));
return internalConfigFiles;
}
private void initCustomPolarisConfigFiles(CompositePropertySource compositePropertySource,
List<ConfigFileGroup> configFileGroups) { List<ConfigFileGroup> configFileGroups) {
String namespace = polarisContextProperties.getNamespace(); String namespace = polarisContextProperties.getNamespace();
for (ConfigFileGroup configFileGroup : configFileGroups) { for (ConfigFileGroup configFileGroup : configFileGroups) {
String group = configFileGroup.getName(); String group = configFileGroup.getName();
if (StringUtils.isEmpty(group)) { if (!StringUtils.hasText(group)) {
throw new IllegalArgumentException("polaris config group name cannot be empty."); throw new IllegalArgumentException("polaris config group name cannot be empty.");
} }

@ -54,13 +54,13 @@ public class DiscoveryPropertiesAutoConfiguration {
private boolean discoveryEnabled = false; private boolean discoveryEnabled = false;
@Bean(name = "polarisProvider") @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ProviderAPI polarisProvider(SDKContext polarisContext) throws PolarisException { public ProviderAPI polarisProvider(SDKContext polarisContext) throws PolarisException {
return DiscoveryAPIFactory.createProviderAPIByContext(polarisContext); return DiscoveryAPIFactory.createProviderAPIByContext(polarisContext);
} }
@Bean(name = "polarisConsumer") @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ConsumerAPI polarisConsumer(SDKContext polarisContext) throws PolarisException { public ConsumerAPI polarisConsumer(SDKContext polarisContext) throws PolarisException {
return DiscoveryAPIFactory.createConsumerAPIByContext(polarisContext); return DiscoveryAPIFactory.createConsumerAPIByContext(polarisContext);

@ -18,14 +18,11 @@
package com.tencent.cloud.polaris; package com.tencent.cloud.polaris;
import javax.annotation.PostConstruct;
import com.tencent.cloud.common.constant.ContextConstant; import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.polaris.context.PolarisConfigModifier; import com.tencent.cloud.polaris.context.PolarisConfigModifier;
import com.tencent.polaris.factory.config.ConfigurationImpl; import com.tencent.polaris.factory.config.ConfigurationImpl;
import com.tencent.polaris.factory.config.consumer.DiscoveryConfigImpl; import com.tencent.polaris.factory.config.consumer.DiscoveryConfigImpl;
import com.tencent.polaris.factory.config.provider.RegisterConfigImpl; import com.tencent.polaris.factory.config.provider.RegisterConfigImpl;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -42,11 +39,6 @@ import org.springframework.core.env.Environment;
@ConfigurationProperties("spring.cloud.polaris.discovery") @ConfigurationProperties("spring.cloud.polaris.discovery")
public class PolarisDiscoveryProperties { public class PolarisDiscoveryProperties {
/**
* The polaris authentication token.
*/
private String token;
/** /**
* Namespace, separation registry of different environments. * Namespace, separation registry of different environments.
*/ */
@ -59,6 +51,11 @@ public class PolarisDiscoveryProperties {
@Value("${spring.cloud.polaris.discovery.service:${spring.cloud.polaris.service:${spring.application.name:}}}") @Value("${spring.cloud.polaris.discovery.service:${spring.cloud.polaris.service:${spring.application.name:}}}")
private String service; private String service;
/**
* The polaris authentication token.
*/
private String token;
/** /**
* Load balance weight. * Load balance weight.
*/ */
@ -79,7 +76,7 @@ public class PolarisDiscoveryProperties {
/** /**
* Port of instance. * Port of instance.
*/ */
@Value("${server.port:}") @Value("${server.port:8080}")
private int port; private int port;
/** /**
@ -113,26 +110,7 @@ public class PolarisDiscoveryProperties {
@Autowired @Autowired
private Environment environment; private Environment environment;
/**
* Init properties.
*/
@PostConstruct
public void init() {
if (StringUtils.isEmpty(this.getNamespace())) {
this.setNamespace(environment.resolvePlaceholders("${spring.cloud.polaris.discovery.namespace:}"));
}
if (StringUtils.isEmpty(this.getService())) {
this.setService(environment.resolvePlaceholders("${spring.cloud.polaris.discovery.service:}"));
}
if (StringUtils.isEmpty(this.getToken())) {
this.setToken(environment.resolvePlaceholders("${spring.cloud.polaris.discovery.token:}"));
}
}
public boolean isHeartbeatEnabled() { public boolean isHeartbeatEnabled() {
if (null == heartbeatEnabled) {
return false;
}
return heartbeatEnabled; return heartbeatEnabled;
} }
@ -230,10 +208,20 @@ public class PolarisDiscoveryProperties {
@Override @Override
public String toString() { public String toString() {
return "PolarisProperties{" + "token='" + token + '\'' + ", namespace='" + namespace + '\'' + ", service='" return "PolarisDiscoveryProperties{" +
+ service + '\'' + ", weight=" + weight + ", version='" + version + '\'' + ", protocol='" + protocol "namespace='" + namespace + '\'' +
+ '\'' + ", port=" + port + '\'' + ", registerEnabled=" + registerEnabled + ", heartbeatEnabled=" ", service='" + service + '\'' +
+ heartbeatEnabled + ", healthCheckUrl=" + healthCheckUrl + ", environment=" + environment + '}'; ", token='" + token + '\'' +
", weight=" + weight +
", version='" + version + '\'' +
", protocol='" + protocol + '\'' +
", port=" + port +
", enabled=" + enabled +
", registerEnabled=" + registerEnabled +
", heartbeatEnabled=" + heartbeatEnabled +
", healthCheckUrl='" + healthCheckUrl + '\'' +
", serviceListRefreshInterval=" + serviceListRefreshInterval +
'}';
} }
@Bean @Bean

@ -32,7 +32,7 @@ public class PolarisDiscoveryClient implements DiscoveryClient {
/** /**
* Polaris Discovery Client Description. * Polaris Discovery Client Description.
*/ */
public final String description = "Spring Cloud Polaris Discovery Client"; public final String description = "Spring Cloud Tencent Polaris Discovery Client.";
private final PolarisServiceDiscovery polarisServiceDiscovery; private final PolarisServiceDiscovery polarisServiceDiscovery;

@ -18,22 +18,15 @@
package com.tencent.cloud.polaris.discovery; package com.tencent.cloud.polaris.discovery;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties; import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.core.ProviderAPI; import com.tencent.polaris.api.core.ProviderAPI;
import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.rpc.GetAllInstancesRequest; import com.tencent.polaris.api.rpc.GetAllInstancesRequest;
import com.tencent.polaris.api.rpc.GetHealthyInstancesRequest; import com.tencent.polaris.api.rpc.GetHealthyInstancesRequest;
import com.tencent.polaris.api.rpc.GetInstancesRequest;
import com.tencent.polaris.api.rpc.GetServicesRequest; import com.tencent.polaris.api.rpc.GetServicesRequest;
import com.tencent.polaris.api.rpc.InstancesResponse; import com.tencent.polaris.api.rpc.InstancesResponse;
import com.tencent.polaris.api.rpc.ServicesResponse; import com.tencent.polaris.api.rpc.ServicesResponse;
import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.client.api.SDKContext;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -58,32 +51,6 @@ public class PolarisDiscoveryHandler {
@Autowired @Autowired
private ConsumerAPI polarisConsumer; private ConsumerAPI polarisConsumer;
/**
* Get a list of instances after service routing.
* @param service service name
* @return list of instances
*/
@Deprecated
public InstancesResponse getFilteredInstances(String service) {
String namespace = polarisDiscoveryProperties.getNamespace();
GetInstancesRequest getInstancesRequest = new GetInstancesRequest();
getInstancesRequest.setNamespace(namespace);
getInstancesRequest.setService(service);
String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE;
Map<String, String> allTransitiveCustomMetadata = MetadataContextHolder.get()
.getAllTransitiveCustomMetadata();
if (StringUtils.isNotBlank(localNamespace) || StringUtils.isNotBlank(localService)
|| null != allTransitiveCustomMetadata) {
ServiceInfo sourceService = new ServiceInfo();
sourceService.setNamespace(localNamespace);
sourceService.setService(localService);
sourceService.setMetadata(allTransitiveCustomMetadata);
getInstancesRequest.setServiceInfo(sourceService);
}
return polarisConsumer.getInstances(getInstancesRequest);
}
/** /**
* Get a list of healthy instances. * Get a list of healthy instances.
* @param service service name * @param service service name

@ -48,7 +48,7 @@ public class PolarisReactiveDiscoveryClient implements ReactiveDiscoveryClient {
@Override @Override
public String description() { public String description() {
return "Spring Cloud Polaris Reactive Discovery Client"; return "Spring Cloud Tencent Polaris Reactive Discovery Client.";
} }
@Override @Override

@ -1,3 +1,19 @@
/*
* 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.discovery.refresh; package com.tencent.cloud.polaris.discovery.refresh;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -24,7 +40,7 @@ import static com.tencent.cloud.polaris.discovery.refresh.PolarisServiceStatusCh
*/ */
public class PolarisRefreshApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent>, ApplicationEventPublisherAware { public class PolarisRefreshApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent>, ApplicationEventPublisherAware {
private static final Logger LOG = LoggerFactory.getLogger(PolarisRefreshConfiguration.class); private static final Logger LOG = LoggerFactory.getLogger(PolarisRefreshApplicationReadyEventListener.class);
private static final int DELAY = 60; private static final int DELAY = 60;
private final PolarisDiscoveryHandler polarisDiscoveryHandler; private final PolarisDiscoveryHandler polarisDiscoveryHandler;
private final PolarisServiceStatusChangeListener polarisServiceStatusChangeListener; private final PolarisServiceStatusChangeListener polarisServiceStatusChangeListener;

@ -1,3 +1,19 @@
/*
* 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.discovery.refresh; package com.tencent.cloud.polaris.discovery.refresh;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;

@ -1,3 +1,19 @@
/*
* 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.discovery.refresh; package com.tencent.cloud.polaris.discovery.refresh;
import java.util.Set; import java.util.Set;

@ -73,10 +73,18 @@ public class ConsulContextProperties {
@Value("${spring.cloud.consul.discovery.prefer-ip-address:#{'false'}}") @Value("${spring.cloud.consul.discovery.prefer-ip-address:#{'false'}}")
private boolean preferIpAddress; private boolean preferIpAddress;
public String getHost() {
return host;
}
public void setHost(String host) { public void setHost(String host) {
this.host = host; this.host = host;
} }
public int getPort() {
return port;
}
public void setPort(int port) { public void setPort(int port) {
this.port = port; this.port = port;
} }

@ -27,7 +27,7 @@ import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
/** /**
* @author Haotian Zhang * @author Haotian Zhang, Andrew Shan, Jie Cheng
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD }) @Target({ ElementType.TYPE, ElementType.METHOD })

@ -19,9 +19,10 @@
package com.tencent.cloud.polaris.registry; package com.tencent.cloud.polaris.registry;
import java.net.URI; import java.net.URI;
import java.util.Collections; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration; import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties; import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.client.api.SDKContext;
@ -30,6 +31,7 @@ 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.ServiceInstance;
import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.util.CollectionUtils;
/** /**
* Registration object of Polaris. * Registration object of Polaris.
@ -44,11 +46,16 @@ public class PolarisRegistration implements Registration, ServiceInstance {
private final SDKContext polarisContext; private final SDKContext polarisContext;
private final StaticMetadataManager staticMetadataManager;
private Map<String, String> metadata;
public PolarisRegistration(DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration, public PolarisRegistration(DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration,
PolarisDiscoveryProperties polarisDiscoveryProperties, SDKContext context) { PolarisDiscoveryProperties polarisDiscoveryProperties, SDKContext context, StaticMetadataManager staticMetadataManager) {
this.discoveryPropertiesAutoConfiguration = discoveryPropertiesAutoConfiguration; this.discoveryPropertiesAutoConfiguration = discoveryPropertiesAutoConfiguration;
this.polarisDiscoveryProperties = polarisDiscoveryProperties; this.polarisDiscoveryProperties = polarisDiscoveryProperties;
this.polarisContext = context; this.polarisContext = context;
this.staticMetadataManager = staticMetadataManager;
} }
@Override @Override
@ -82,7 +89,13 @@ public class PolarisRegistration implements Registration, ServiceInstance {
@Override @Override
public Map<String, String> getMetadata() { public Map<String, String> getMetadata() {
return Collections.emptyMap(); if (CollectionUtils.isEmpty(metadata)) {
metadata = new HashMap<>();
metadata.putAll(staticMetadataManager.getMergedStaticMetadata());
// location info will be putted both in metadata and instance's field
metadata.putAll(staticMetadataManager.getLocationMetadata());
}
return metadata;
} }
public PolarisDiscoveryProperties getPolarisProperties() { public PolarisDiscoveryProperties getPolarisProperties() {
@ -95,8 +108,12 @@ public class PolarisRegistration implements Registration, ServiceInstance {
@Override @Override
public String toString() { public String toString() {
return "PolarisRegistration{" + "polarisDiscoveryProperties=" + polarisDiscoveryProperties + ", polarisContext=" return "PolarisRegistration{" +
+ polarisContext + '}'; "discoveryPropertiesAutoConfiguration=" + discoveryPropertiesAutoConfiguration +
", polarisDiscoveryProperties=" + polarisDiscoveryProperties +
", polarisContext=" + polarisContext +
", staticMetadataManager=" + staticMetadataManager +
", metadata=" + metadata +
'}';
} }
} }

@ -22,7 +22,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties; import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler; import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler;
import com.tencent.cloud.polaris.util.OkHttpUtil; import com.tencent.cloud.polaris.util.OkHttpUtil;
@ -59,16 +59,16 @@ public class PolarisServiceRegistry implements ServiceRegistry<Registration> {
private final PolarisDiscoveryHandler polarisDiscoveryHandler; private final PolarisDiscoveryHandler polarisDiscoveryHandler;
private final MetadataLocalProperties metadataLocalProperties; private final StaticMetadataManager staticMetadataManager;
private final ScheduledExecutorService heartbeatExecutor; private final ScheduledExecutorService heartbeatExecutor;
public PolarisServiceRegistry(PolarisDiscoveryProperties polarisDiscoveryProperties, public PolarisServiceRegistry(PolarisDiscoveryProperties polarisDiscoveryProperties,
PolarisDiscoveryHandler polarisDiscoveryHandler, PolarisDiscoveryHandler polarisDiscoveryHandler,
MetadataLocalProperties metadataLocalProperties) { StaticMetadataManager staticMetadataManager) {
this.polarisDiscoveryProperties = polarisDiscoveryProperties; this.polarisDiscoveryProperties = polarisDiscoveryProperties;
this.polarisDiscoveryHandler = polarisDiscoveryHandler; this.polarisDiscoveryHandler = polarisDiscoveryHandler;
this.metadataLocalProperties = metadataLocalProperties; this.staticMetadataManager = staticMetadataManager;
if (polarisDiscoveryProperties.isHeartbeatEnabled()) { if (polarisDiscoveryProperties.isHeartbeatEnabled()) {
this.heartbeatExecutor = Executors this.heartbeatExecutor = Executors
@ -94,10 +94,13 @@ public class PolarisServiceRegistry implements ServiceRegistry<Registration> {
instanceRegisterRequest.setPort(registration.getPort()); instanceRegisterRequest.setPort(registration.getPort());
instanceRegisterRequest.setWeight(polarisDiscoveryProperties.getWeight()); instanceRegisterRequest.setWeight(polarisDiscoveryProperties.getWeight());
instanceRegisterRequest.setToken(polarisDiscoveryProperties.getToken()); instanceRegisterRequest.setToken(polarisDiscoveryProperties.getToken());
instanceRegisterRequest.setRegion(staticMetadataManager.getRegion());
instanceRegisterRequest.setZone(staticMetadataManager.getZone());
instanceRegisterRequest.setCampus(staticMetadataManager.getCampus());
if (null != heartbeatExecutor) { if (null != heartbeatExecutor) {
instanceRegisterRequest.setTtl(ttl); instanceRegisterRequest.setTtl(ttl);
} }
instanceRegisterRequest.setMetadata(metadataLocalProperties.getContent()); instanceRegisterRequest.setMetadata(registration.getMetadata());
instanceRegisterRequest.setProtocol(polarisDiscoveryProperties.getProtocol()); instanceRegisterRequest.setProtocol(polarisDiscoveryProperties.getProtocol());
instanceRegisterRequest.setVersion(polarisDiscoveryProperties.getVersion()); instanceRegisterRequest.setVersion(polarisDiscoveryProperties.getVersion());
try { try {
@ -105,7 +108,7 @@ public class PolarisServiceRegistry implements ServiceRegistry<Registration> {
providerClient.register(instanceRegisterRequest); providerClient.register(instanceRegisterRequest);
log.info("polaris registry, {} {} {}:{} {} register finished", polarisDiscoveryProperties.getNamespace(), log.info("polaris registry, {} {} {}:{} {} register finished", polarisDiscoveryProperties.getNamespace(),
registration.getServiceId(), registration.getHost(), registration.getPort(), registration.getServiceId(), registration.getHost(), registration.getPort(),
metadataLocalProperties.getContent()); staticMetadataManager.getMergedStaticMetadata());
if (null != heartbeatExecutor) { if (null != heartbeatExecutor) {
InstanceHeartbeatRequest heartbeatRequest = new InstanceHeartbeatRequest(); InstanceHeartbeatRequest heartbeatRequest = new InstanceHeartbeatRequest();

@ -18,7 +18,7 @@
package com.tencent.cloud.polaris.registry; package com.tencent.cloud.polaris.registry;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration; import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties; import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration; import com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration;
@ -44,23 +44,25 @@ import org.springframework.context.annotation.Configuration;
@EnableConfigurationProperties @EnableConfigurationProperties
@ConditionalOnPolarisRegisterEnabled @ConditionalOnPolarisRegisterEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class, AutoServiceRegistrationAutoConfiguration.class, @AutoConfigureAfter({AutoServiceRegistrationConfiguration.class, AutoServiceRegistrationAutoConfiguration.class,
PolarisDiscoveryAutoConfiguration.class }) PolarisDiscoveryAutoConfiguration.class})
public class PolarisServiceRegistryAutoConfiguration { public class PolarisServiceRegistryAutoConfiguration {
@Bean @Bean
public PolarisServiceRegistry polarisServiceRegistry( public PolarisServiceRegistry polarisServiceRegistry(
PolarisDiscoveryProperties polarisDiscoveryProperties, PolarisDiscoveryHandler polarisDiscoveryHandler, PolarisDiscoveryProperties polarisDiscoveryProperties, PolarisDiscoveryHandler polarisDiscoveryHandler,
MetadataLocalProperties metadataLocalProperties) { StaticMetadataManager staticMetadataManager) {
return new PolarisServiceRegistry(polarisDiscoveryProperties, polarisDiscoveryHandler, metadataLocalProperties); return new PolarisServiceRegistry(polarisDiscoveryProperties, polarisDiscoveryHandler, staticMetadataManager);
} }
@Bean @Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class) @ConditionalOnBean(AutoServiceRegistrationProperties.class)
public PolarisRegistration polarisRegistration( public PolarisRegistration polarisRegistration(
DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration, DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration,
PolarisDiscoveryProperties polarisDiscoveryProperties, SDKContext context) { PolarisDiscoveryProperties polarisDiscoveryProperties, SDKContext context,
return new PolarisRegistration(discoveryPropertiesAutoConfiguration, polarisDiscoveryProperties, context); StaticMetadataManager staticMetadataManager) {
return new PolarisRegistration(discoveryPropertiesAutoConfiguration,
polarisDiscoveryProperties, context, staticMetadataManager);
} }
@Bean @Bean

@ -0,0 +1,90 @@
/*
* 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;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler;
import com.tencent.cloud.polaris.extend.consul.ConsulContextProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.core.ProviderAPI;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link DiscoveryPropertiesAutoConfiguration}.
*
* @author Haotian Zhang
*/
public class DiscoveryPropertiesAutoConfigurationTest {
@Test
public void testDefaultInitialization() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(PolarisContextAutoConfiguration.class,
DiscoveryPropertiesAutoConfiguration.class));
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(DiscoveryPropertiesAutoConfiguration.class);
assertThat(context).hasSingleBean(PolarisDiscoveryProperties.class);
assertThat(context).hasSingleBean(ConsulContextProperties.class);
assertThat(context).hasSingleBean(ProviderAPI.class);
assertThat(context).hasSingleBean(ConsumerAPI.class);
assertThat(context).hasSingleBean(PolarisDiscoveryHandler.class);
assertThat(context).hasSingleBean(DiscoveryConfigModifier.class);
});
}
@Test
public void testInit() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(PolarisContextAutoConfiguration.class,
TestConfiguration.class,
DiscoveryPropertiesAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.discovery.register=false")
.withPropertyValues("spring.cloud.consul.discovery.register=false")
.withPropertyValues("spring.cloud.consul.discovery.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(DiscoveryPropertiesAutoConfiguration.class);
DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration = context.getBean(DiscoveryPropertiesAutoConfiguration.class);
assertThat(discoveryPropertiesAutoConfiguration.isRegisterEnabled()).isFalse();
assertThat(discoveryPropertiesAutoConfiguration.isDiscoveryEnabled()).isFalse();
});
}
@Configuration
static class TestConfiguration {
@Bean
public PolarisDiscoveryProperties polarisDiscoveryProperties() {
PolarisDiscoveryProperties polarisDiscoveryProperties = new PolarisDiscoveryProperties();
polarisDiscoveryProperties.setEnabled(false);
return polarisDiscoveryProperties;
}
@Bean
public ConsulContextProperties consulContextProperties() {
ConsulContextProperties consulContextProperties = new ConsulContextProperties();
consulContextProperties.setEnabled(true);
return consulContextProperties;
}
}
}

@ -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.polaris;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link DiscoveryPropertiesBootstrapAutoConfiguration}.
*
* @author Haotian Zhang
*/
public class DiscoveryPropertiesBootstrapAutoConfigurationTest {
@Test
public void testDefaultInitialization() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(PolarisContextAutoConfiguration.class,
DiscoveryPropertiesBootstrapAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.enabled=true");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(DiscoveryPropertiesBootstrapAutoConfiguration.class);
assertThat(context).hasSingleBean(DiscoveryPropertiesAutoConfiguration.class);
});
}
}

@ -0,0 +1,102 @@
/*
* 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;
import org.junit.Test;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.PORT;
import static com.tencent.polaris.test.common.Consts.PROVIDER_TOKEN;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisDiscoveryProperties}.
*
* @author Haotian Zhang
*/
public class PolarisDiscoveryPropertiesTest {
@Test
public void testGetAndSet() {
PolarisDiscoveryProperties polarisDiscoveryProperties = new PolarisDiscoveryProperties();
// HeartbeatEnabled
polarisDiscoveryProperties.setHeartbeatEnabled(true);
assertThat(polarisDiscoveryProperties.isHeartbeatEnabled()).isTrue();
// Namespace
polarisDiscoveryProperties.setNamespace(NAMESPACE_TEST);
assertThat(polarisDiscoveryProperties.getNamespace()).isEqualTo(NAMESPACE_TEST);
// Weight
polarisDiscoveryProperties.setWeight(10);
assertThat(polarisDiscoveryProperties.getWeight()).isEqualTo(10);
// Service
polarisDiscoveryProperties.setService(SERVICE_PROVIDER);
assertThat(polarisDiscoveryProperties.getService()).isEqualTo(SERVICE_PROVIDER);
// Enabled
polarisDiscoveryProperties.setEnabled(true);
assertThat(polarisDiscoveryProperties.isEnabled()).isTrue();
// RegisterEnabled
polarisDiscoveryProperties.setRegisterEnabled(true);
assertThat(polarisDiscoveryProperties.isRegisterEnabled()).isTrue();
// Token
polarisDiscoveryProperties.setToken(PROVIDER_TOKEN);
assertThat(polarisDiscoveryProperties.getToken()).isEqualTo(PROVIDER_TOKEN);
// Version
polarisDiscoveryProperties.setVersion("1.0.0");
assertThat(polarisDiscoveryProperties.getVersion()).isEqualTo("1.0.0");
// HTTP
polarisDiscoveryProperties.setProtocol("HTTP");
assertThat(polarisDiscoveryProperties.getProtocol()).isEqualTo("HTTP");
// Port
polarisDiscoveryProperties.setPort(PORT);
assertThat(polarisDiscoveryProperties.getPort()).isEqualTo(PORT);
// HealthCheckUrl
polarisDiscoveryProperties.setHealthCheckUrl("/health");
assertThat(polarisDiscoveryProperties.getHealthCheckUrl()).isEqualTo("/health");
// ServiceListRefreshInterval
polarisDiscoveryProperties.setServiceListRefreshInterval(1000L);
assertThat(polarisDiscoveryProperties.getServiceListRefreshInterval()).isEqualTo(1000L);
assertThat(polarisDiscoveryProperties.toString())
.isEqualTo("PolarisDiscoveryProperties{"
+ "namespace='Test'"
+ ", service='java_provider_test'"
+ ", token='19485a7674294e3c88dba293373c1534'"
+ ", weight=10, version='1.0.0'"
+ ", protocol='HTTP'"
+ ", port=9091"
+ ", enabled=true"
+ ", registerEnabled=true"
+ ", heartbeatEnabled=true"
+ ", healthCheckUrl='/health'"
+ ", serviceListRefreshInterval=1000}");
}
}

@ -38,7 +38,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisDiscoveryAutoConfiguration} * Test for {@link PolarisDiscoveryAutoConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -34,7 +34,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisDiscoveryClientConfiguration} * Test for {@link PolarisDiscoveryClientConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -36,7 +36,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* Test for {@link PolarisDiscoveryClient} * Test for {@link PolarisDiscoveryClient}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@ -71,4 +71,8 @@ public class PolarisDiscoveryClientTest {
} }
@Test
public void testDescription() {
assertThat(client.description()).isEqualTo("Spring Cloud Tencent Polaris Discovery Client.");
}
} }

@ -41,7 +41,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisServiceDiscovery} * Test for {@link PolarisServiceDiscovery}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -35,7 +35,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisReactiveDiscoveryClientConfiguration} * Test for {@link PolarisReactiveDiscoveryClientConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -20,6 +20,7 @@ package com.tencent.cloud.polaris.discovery.reactive;
import java.util.Arrays; import java.util.Arrays;
import com.tencent.cloud.polaris.discovery.PolarisServiceDiscovery; import com.tencent.cloud.polaris.discovery.PolarisServiceDiscovery;
import com.tencent.polaris.api.exception.ErrorCode;
import com.tencent.polaris.api.exception.PolarisException; import com.tencent.polaris.api.exception.PolarisException;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -33,10 +34,13 @@ import org.springframework.cloud.client.ServiceInstance;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* Test for {@link PolarisReactiveDiscoveryClient} * Test for {@link PolarisReactiveDiscoveryClient}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@ -46,30 +50,57 @@ public class PolarisReactiveDiscoveryClientTest {
@Mock @Mock
private PolarisServiceDiscovery serviceDiscovery; private PolarisServiceDiscovery serviceDiscovery;
@Mock
private ServiceInstance serviceInstance;
@InjectMocks @InjectMocks
private PolarisReactiveDiscoveryClient client; private PolarisReactiveDiscoveryClient client;
private int count = 0;
@Test @Test
public void testGetInstances() throws PolarisException { public void testGetInstances() throws PolarisException {
when(serviceDiscovery.getInstances(SERVICE_PROVIDER)).thenReturn(singletonList(serviceInstance)); when(serviceDiscovery.getInstances(anyString())).thenAnswer(invocation -> {
String serviceName = invocation.getArgument(0);
if (SERVICE_PROVIDER.equalsIgnoreCase(serviceName)) {
return singletonList(mock(ServiceInstance.class));
}
else {
throw new PolarisException(ErrorCode.UNKNOWN_SERVER_ERROR);
}
});
// Normal
Flux<ServiceInstance> instances = this.client.getInstances(SERVICE_PROVIDER); Flux<ServiceInstance> instances = this.client.getInstances(SERVICE_PROVIDER);
StepVerifier.create(instances).expectNextCount(1).expectComplete().verify(); StepVerifier.create(instances).expectNextCount(1).expectComplete().verify();
// PolarisException
instances = this.client.getInstances(SERVICE_PROVIDER + 1);
StepVerifier.create(instances).expectNextCount(0).expectComplete().verify();
} }
@Test @Test
public void testGetServices() throws PolarisException { public void testGetServices() throws PolarisException {
when(serviceDiscovery.getServices()).thenReturn(Arrays.asList(SERVICE_PROVIDER + 1, SERVICE_PROVIDER + 2)); when(serviceDiscovery.getServices()).thenAnswer(invocation -> {
if (count == 0) {
count++;
return Arrays.asList(SERVICE_PROVIDER + 1, SERVICE_PROVIDER + 2);
}
else {
throw new PolarisException(ErrorCode.UNKNOWN_SERVER_ERROR);
}
});
// Normal
Flux<String> services = this.client.getServices(); Flux<String> services = this.client.getServices();
StepVerifier.create(services).expectNext(SERVICE_PROVIDER + 1, SERVICE_PROVIDER + 2).expectComplete().verify(); StepVerifier.create(services).expectNext(SERVICE_PROVIDER + 1, SERVICE_PROVIDER + 2).expectComplete().verify();
// PolarisException
services = this.client.getServices();
StepVerifier.create(services).expectNextCount(0).expectComplete().verify();
} }
@Test
public void testDescription() {
assertThat(client.description()).isEqualTo("Spring Cloud Tencent Polaris Reactive Discovery Client.");
}
} }

@ -0,0 +1,114 @@
/*
* 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.discovery.refresh;
import java.lang.reflect.Field;
import java.util.Collections;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.ServiceEventKey;
import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.client.pojo.ServiceInstancesByProto;
import com.tencent.polaris.client.pojo.ServicesByProto;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import static com.tencent.polaris.test.common.Consts.HOST;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.PORT;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Test for {@link PolarisServiceStatusChangeListener}.
*
* @author Haotian Zhang
*/
public class PolarisServiceStatusChangeListenerTest {
private ApplicationEventPublisher publisher;
@Before
public void setUp() {
publisher = mock(ApplicationEventPublisher.class);
doNothing().when(publisher).publishEvent(any(ApplicationEvent.class));
}
@Test
public void testOnResourceUpdated() {
PolarisServiceStatusChangeListener polarisServiceStatusChangeListener = new PolarisServiceStatusChangeListener();
polarisServiceStatusChangeListener.setApplicationEventPublisher(publisher);
// Service update event
ServiceEventKey serviceUpdateEventKey = new ServiceEventKey(new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER), ServiceEventKey.EventType.SERVICE);
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setNamespace(NAMESPACE_TEST);
serviceInfo.setService(SERVICE_PROVIDER);
// Need update
ServicesByProto oldServices = new ServicesByProto(Collections.emptyList());
ServicesByProto newServices = new ServicesByProto(Collections.singletonList(serviceInfo));
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldServices, newServices);
verify(publisher, times(1)).publishEvent(any(ApplicationEvent.class));
// No need update
oldServices = new ServicesByProto(Collections.singletonList(serviceInfo));
newServices = new ServicesByProto(Collections.singletonList(serviceInfo));
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldServices, newServices);
verify(publisher, times(1)).publishEvent(any(ApplicationEvent.class));
// Instance update event
ServiceEventKey instanceUpdateEventKey = new ServiceEventKey(new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER), ServiceEventKey.EventType.INSTANCE);
DefaultInstance instance = new DefaultInstance();
instance.setNamespace(NAMESPACE_TEST);
instance.setService(SERVICE_PROVIDER);
instance.setHost(HOST);
instance.setPort(PORT);
try {
Field instances = ServiceInstancesByProto.class.getDeclaredField("instances");
instances.setAccessible(true);
// Need update
ServiceInstancesByProto oldInstances = new ServiceInstancesByProto();
instances.set(oldInstances, Collections.emptyList());
ServiceInstancesByProto newInstances = new ServiceInstancesByProto();
instances.set(newInstances, Collections.singletonList(instance));
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldInstances, newInstances);
verify(publisher, times(2)).publishEvent(any(ApplicationEvent.class));
// No need update
oldInstances = new ServiceInstancesByProto();
instances.set(oldInstances, Collections.singletonList(instance));
newInstances = new ServiceInstancesByProto();
instances.set(newInstances, Collections.singletonList(instance));
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldInstances, newInstances);
verify(publisher, times(2)).publishEvent(any(ApplicationEvent.class));
}
catch (NoSuchFieldException | IllegalAccessException e) {
Assertions.fail("Exception encountered.", e);
}
}
}

@ -0,0 +1,90 @@
/*
* 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.extend.consul;
import java.util.List;
import java.util.Map;
import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.factory.config.global.ServerConnectorConfigImpl;
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 com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.INSTANCE_ID_KEY;
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.IP_ADDRESS_KEY;
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.PREFER_IP_ADDRESS_KEY;
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.SERVICE_NAME_KEY;
import static com.tencent.polaris.test.common.Consts.HOST;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link ConsulContextProperties}.
*
* @author Haotian Zhang
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConsulContextPropertiesTest.TestApplication.class)
@ActiveProfiles("test")
public class ConsulContextPropertiesTest {
@Autowired
private ConsulContextProperties consulContextProperties;
@Autowired
private SDKContext sdkContext;
@Test
public void testDefaultInitialization() {
assertThat(consulContextProperties).isNotNull();
assertThat(consulContextProperties.isEnabled()).isTrue();
assertThat(consulContextProperties.getHost()).isEqualTo("127.0.0.1");
assertThat(consulContextProperties.getPort()).isEqualTo(8500);
assertThat(consulContextProperties.isRegister()).isTrue();
assertThat(consulContextProperties.isDiscoveryEnabled()).isTrue();
}
@Test
public void testModify() {
assertThat(sdkContext).isNotNull();
com.tencent.polaris.api.config.Configuration configuration = sdkContext.getConfig();
List<ServerConnectorConfigImpl> serverConnectorConfigs = configuration.getGlobal().getServerConnectors();
Map<String, String> metadata = null;
for (ServerConnectorConfigImpl serverConnectorConfig : serverConnectorConfigs) {
if (serverConnectorConfig.getId().equals("consul")) {
metadata = serverConnectorConfig.getMetadata();
}
}
assertThat(metadata).isNotNull();
assertThat(metadata.get(SERVICE_NAME_KEY)).isEqualTo(SERVICE_PROVIDER);
assertThat(metadata.get(INSTANCE_ID_KEY)).isEqualTo("ins-test");
assertThat(metadata.get(PREFER_IP_ADDRESS_KEY)).isEqualTo("true");
assertThat(metadata.get(IP_ADDRESS_KEY)).isEqualTo(HOST);
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,144 @@
/*
* 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.registry;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import static com.tencent.polaris.test.common.Consts.PORT;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
/**
* Test for {@link PolarisAutoServiceRegistration}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisAutoServiceRegistrationTest {
@Mock
private ServiceRegistry<Registration> serviceRegistry;
@Mock
private AutoServiceRegistrationProperties autoServiceRegistrationProperties;
@Mock
private PolarisDiscoveryProperties polarisDiscoveryProperties;
@Mock
private ApplicationContext applicationContext;
@Mock
private Environment environment;
@Mock
private PolarisRegistration registration;
private PolarisAutoServiceRegistration polarisAutoServiceRegistration;
@Before
public void setUp() {
doReturn(polarisDiscoveryProperties).when(registration).getPolarisProperties();
doNothing().when(serviceRegistry).register(nullable(Registration.class));
polarisAutoServiceRegistration =
new PolarisAutoServiceRegistration(serviceRegistry, autoServiceRegistrationProperties, registration);
doReturn(environment).when(applicationContext).getEnvironment();
polarisAutoServiceRegistration.setApplicationContext(applicationContext);
}
@Test
public void testRegister() {
doReturn(false).when(registration).isRegisterEnabled();
try {
polarisAutoServiceRegistration.register();
}
catch (Exception e) {
fail();
}
doReturn(true).when(registration).isRegisterEnabled();
doReturn(-1).when(registration).getPort();
try {
polarisAutoServiceRegistration.register();
}
catch (Exception e) {
fail();
}
doReturn(PORT).when(registration).getPort();
try {
polarisAutoServiceRegistration.register();
}
catch (Exception e) {
fail();
}
}
@Test
public void testGetManagementRegistration() {
assertThat(polarisAutoServiceRegistration.getManagementRegistration()).isNull();
}
@Test
public void testRegisterManagement() {
doReturn(false).when(registration).isRegisterEnabled();
try {
polarisAutoServiceRegistration.registerManagement();
}
catch (Exception e) {
fail();
}
doReturn(true).when(registration).isRegisterEnabled();
try {
polarisAutoServiceRegistration.registerManagement();
}
catch (Exception e) {
fail();
}
}
@Test
public void testGetAppName() {
doReturn("application").when(environment).getProperty(anyString(), anyString());
assertThat(polarisAutoServiceRegistration.getAppName()).isEqualTo("application");
doReturn(SERVICE_PROVIDER).when(polarisDiscoveryProperties).getService();
assertThat(polarisAutoServiceRegistration.getAppName()).isEqualTo(SERVICE_PROVIDER);
}
}

@ -0,0 +1,133 @@
/*
* 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.registry;
import java.util.Collections;
import java.util.Map;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.polaris.api.config.Configuration;
import com.tencent.polaris.api.config.global.APIConfig;
import com.tencent.polaris.api.config.global.GlobalConfig;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static com.tencent.polaris.test.common.Consts.HOST;
import static com.tencent.polaris.test.common.Consts.PORT;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
/**
* Test for {@link PolarisRegistration}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisRegistrationTest {
private PolarisRegistration polarisRegistration;
@Before
public void setUp() {
// mock DiscoveryPropertiesAutoConfiguration
DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration =
mock(DiscoveryPropertiesAutoConfiguration.class);
doReturn(true).when(discoveryPropertiesAutoConfiguration).isRegisterEnabled();
// mock PolarisDiscoveryProperties
PolarisDiscoveryProperties polarisDiscoveryProperties = mock(PolarisDiscoveryProperties.class);
doReturn(SERVICE_PROVIDER).when(polarisDiscoveryProperties).getService();
doReturn(PORT).when(polarisDiscoveryProperties).getPort();
doReturn("http").when(polarisDiscoveryProperties).getProtocol();
// mock SDKContext
APIConfig apiConfig = mock(APIConfig.class);
doReturn(HOST).when(apiConfig).getBindIP();
GlobalConfig globalConfig = mock(GlobalConfig.class);
doReturn(apiConfig).when(globalConfig).getAPI();
Configuration configuration = mock(Configuration.class);
doReturn(globalConfig).when(configuration).getGlobal();
SDKContext polarisContext = mock(SDKContext.class);
doReturn(configuration).when(polarisContext).getConfig();
// mock StaticMetadataManager
StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class);
doReturn(Collections.singletonMap("key1", "value1")).when(staticMetadataManager).getMergedStaticMetadata();
doReturn(Collections.singletonMap("key2", "value2")).when(staticMetadataManager).getLocationMetadata();
polarisRegistration = new PolarisRegistration(
discoveryPropertiesAutoConfiguration, polarisDiscoveryProperties, polarisContext, staticMetadataManager);
}
@Test
public void testGetServiceId() {
assertThat(polarisRegistration.getServiceId()).isEqualTo(SERVICE_PROVIDER);
}
@Test
public void testGetHost() {
assertThat(polarisRegistration.getHost()).isEqualTo(HOST);
}
@Test
public void testGetPort() {
assertThat(polarisRegistration.getPort()).isEqualTo(PORT);
}
@Test
public void testIsSecure() {
assertThat(polarisRegistration.isSecure()).isFalse();
}
@Test
public void testGetUri() {
assertThat(polarisRegistration.getUri().toString()).isEqualTo("http://" + HOST + ":" + PORT);
}
@Test
public void testGetMetadata() {
Map<String, String> metadata = polarisRegistration.getMetadata();
assertThat(metadata).isNotNull();
assertThat(metadata).isNotEmpty();
assertThat(metadata.size()).isEqualTo(2);
assertThat(metadata.get("key1")).isEqualTo("value1");
assertThat(metadata.get("key2")).isEqualTo("value2");
}
@Test
public void testGetPolarisProperties() {
assertThat(polarisRegistration.getPolarisProperties()).isNotNull();
}
@Test
public void testIsRegisterEnabled() {
assertThat(polarisRegistration.isRegisterEnabled()).isTrue();
}
@Test
public void testToString() {
System.out.println(polarisRegistration);
}
}

@ -37,7 +37,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisServiceRegistryAutoConfiguration} * Test for {@link PolarisServiceRegistryAutoConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -30,7 +30,6 @@ import org.mockito.Mockito;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
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.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
@ -38,10 +37,11 @@ import static com.tencent.polaris.test.common.Consts.PORT;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* Test for {@link PolarisServiceRegistry} * Test for {@link PolarisServiceRegistry}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@ -105,9 +105,24 @@ public class PolarisServiceRegistryTest {
}); });
} }
@Test
public void testDeRegister() {
this.contextRunner.run(context -> {
PolarisServiceRegistry registry = context
.getBean(PolarisServiceRegistry.class);
PolarisRegistration registration = Mockito.mock(PolarisRegistration.class);
doReturn(null).when(registration).getServiceId();
try {
registry.deregister(registration);
}
catch (Throwable throwable) {
fail();
}
});
}
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@EnableDiscoveryClient
static class PolarisPropertiesConfiguration { static class PolarisPropertiesConfiguration {
} }

@ -0,0 +1,60 @@
/*
* 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.util;
import org.assertj.core.util.Maps;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link OkHttpUtil}.
*
* @author Haotian Zhang
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = OkHttpUtilTest.TestApplication.class)
public class OkHttpUtilTest {
@LocalServerPort
private int port;
@Test
public void testGet() {
assertThat(OkHttpUtil.get("http://localhost:" + port + "/test", Maps.newHashMap("key", "value"))).isTrue();
assertThat(OkHttpUtil.get("http://localhost:" + port + "/error", Maps.newHashMap("key", "value"))).isFalse();
assertThat(OkHttpUtil.get("http://localhost:55555/error", Maps.newHashMap("key", "value"))).isFalse();
}
@SpringBootApplication
@RestController
static class TestApplication {
@GetMapping("/test")
public String test() {
return "test";
}
}
}

@ -0,0 +1,24 @@
server:
port: 48084
spring:
application:
name: java_provider_test
cloud:
polaris:
address: grpc://127.0.0.1:8091
namespace: Test
enabled: true
discovery:
enabled: true
register: true
consul:
port: 8500
host: 127.0.0.1
enabled: true
discovery:
enabled: true
register: true
instance-id: ins-test
service-name: ${spring.application.name}
ip-address: 127.0.0.1
prefer-ip-address: true

@ -34,10 +34,6 @@
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId> <artifactId>router-nearby</artifactId>
</exclusion> </exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-metadata</artifactId>
</exclusion>
<exclusion> <exclusion>
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.polaris</groupId>
<artifactId>router-canary</artifactId> <artifactId>router-canary</artifactId>
@ -87,5 +83,23 @@
<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>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

@ -0,0 +1,72 @@
/*
* 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.ratelimit;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RateLimitProto;
import org.springframework.util.CollectionUtils;
/**
* resolve labels from rate limit rule.
*
*@author lepdou 2022-05-13
*/
public class RateLimitRuleLabelResolver {
private final ServiceRuleManager serviceRuleManager;
public RateLimitRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
this.serviceRuleManager = serviceRuleManager;
}
public Set<String> getExpressionLabelKeys(String namespace, String service) {
RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service);
if (rateLimitRule == null) {
return Collections.emptySet();
}
List<RateLimitProto.Rule> rules = rateLimitRule.getRulesList();
if (CollectionUtils.isEmpty(rules)) {
return Collections.emptySet();
}
Set<String> expressionLabels = new HashSet<>();
for (RateLimitProto.Rule rule : rules) {
Map<String, ModelProto.MatchString> labels = rule.getLabelsMap();
if (CollectionUtils.isEmpty(labels)) {
return Collections.emptySet();
}
for (String key : labels.keySet()) {
if (ExpressionLabelUtils.isExpressionLabel(key)) {
expressionLabels.add(key);
}
}
}
return expressionLabels;
}
}

@ -0,0 +1,75 @@
/*
* 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.ratelimit.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.factory.config.ConfigurationImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Autoconfiguration of rate limit at bootstrap phase.
*
* @author Haotian Zhang
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
@ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true)
public class PolarisRateLimitBootstrapConfiguration {
@Bean
public PolarisRateLimitProperties polarisRateLimitProperties() {
return new PolarisRateLimitProperties();
}
@Bean
public RateLimitConfigModifier rateLimitConfigModifier(PolarisRateLimitProperties polarisRateLimitProperties) {
return new RateLimitConfigModifier(polarisRateLimitProperties);
}
/**
* Config modifier for rate limit.
*
* @author Haotian Zhang
*/
public static class RateLimitConfigModifier implements PolarisConfigModifier {
private PolarisRateLimitProperties polarisRateLimitProperties;
public RateLimitConfigModifier(PolarisRateLimitProperties polarisRateLimitProperties) {
this.polarisRateLimitProperties = polarisRateLimitProperties;
}
@Override
public void modify(ConfigurationImpl configuration) {
// Update MaxQueuingTime.
configuration.getProvider().getRateLimit()
.setMaxQueuingTime(polarisRateLimitProperties.getMaxQueuingTime());
}
@Override
public int getOrder() {
return ContextConstant.ModifierOrder.CIRCUIT_BREAKER_ORDER;
}
}
}

@ -13,11 +13,15 @@
* 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.ratelimit.config; package com.tencent.cloud.polaris.ratelimit.config;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
@ -27,6 +31,7 @@ import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.factory.LimitAPIFactory; import com.tencent.polaris.ratelimit.factory.LimitAPIFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
@ -42,17 +47,15 @@ import static javax.servlet.DispatcherType.INCLUDE;
import static javax.servlet.DispatcherType.REQUEST; import static javax.servlet.DispatcherType.REQUEST;
/** /**
* Configuration of rate limit.
*
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@Configuration @Configuration
@ConditionalOnPolarisEnabled @ConditionalOnPolarisEnabled
@AutoConfigureAfter(PolarisContextAutoConfiguration.class)
@ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true) @ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true)
public class RateLimitConfiguration { public class PolarisRateLimitConfiguration {
@Bean
public PolarisRateLimitProperties polarisRateLimitProperties() {
return new PolarisRateLimitProperties();
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ -60,6 +63,11 @@ public class RateLimitConfiguration {
return LimitAPIFactory.createLimitAPIByContext(polarisContext); return LimitAPIFactory.createLimitAPIByContext(polarisContext);
} }
@Bean
public RateLimitRuleLabelResolver rateLimitRuleLabelService(ServiceRuleManager serviceRuleManager) {
return new RateLimitRuleLabelResolver(serviceRuleManager);
}
/** /**
* Create when web application type is SERVLET. * Create when web application type is SERVLET.
*/ */
@ -71,9 +79,10 @@ public class RateLimitConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI, public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI,
@Nullable PolarisRateLimiterLabelServletResolver labelResolver, @Nullable PolarisRateLimiterLabelServletResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) { PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
return new QuotaCheckServletFilter(limitAPI, labelResolver, return new QuotaCheckServletFilter(limitAPI, labelResolver,
polarisRateLimitProperties); polarisRateLimitProperties, rateLimitRuleLabelResolver);
} }
@Bean @Bean
@ -99,9 +108,10 @@ public class RateLimitConfiguration {
@Bean @Bean
public QuotaCheckReactiveFilter quotaCheckReactiveFilter(LimitAPI limitAPI, public QuotaCheckReactiveFilter quotaCheckReactiveFilter(LimitAPI limitAPI,
@Nullable PolarisRateLimiterLabelReactiveResolver labelResolver, @Nullable PolarisRateLimiterLabelReactiveResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) { PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
return new QuotaCheckReactiveFilter(limitAPI, labelResolver, return new QuotaCheckReactiveFilter(limitAPI, labelResolver,
polarisRateLimitProperties); polarisRateLimitProperties, rateLimitRuleLabelResolver);
} }
} }

@ -44,6 +44,11 @@ public class PolarisRateLimitProperties {
*/ */
private int rejectHttpCode = HttpStatus.TOO_MANY_REQUESTS.value(); private int rejectHttpCode = HttpStatus.TOO_MANY_REQUESTS.value();
/**
* Max queuing time when using unirate.
*/
private long maxQueuingTime = 1000L;
public String getRejectRequestTips() { public String getRejectRequestTips() {
return rejectRequestTips; return rejectRequestTips;
} }
@ -68,4 +73,11 @@ public class PolarisRateLimitProperties {
this.rejectHttpCode = rejectHttpCode; this.rejectHttpCode = rejectHttpCode;
} }
public long getMaxQueuingTime() {
return maxQueuingTime;
}
public void setMaxQueuingTime(long maxQueuingTime) {
this.maxQueuingTime = maxQueuingTime;
}
} }

@ -21,10 +21,14 @@ package com.tencent.cloud.polaris.ratelimit.filter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import com.google.common.collect.Maps;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
@ -42,7 +46,6 @@ import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebFilterChain;
@ -52,7 +55,7 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB
/** /**
* Reactive filter to check quota. * Reactive filter to check quota.
* *
* @author Haotian Zhang * @author Haotian Zhang, lepdou
*/ */
public class QuotaCheckReactiveFilter implements WebFilter, Ordered { public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
@ -64,14 +67,18 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
private final PolarisRateLimitProperties polarisRateLimitProperties; private final PolarisRateLimitProperties polarisRateLimitProperties;
private final RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
private String rejectTips; private String rejectTips;
public QuotaCheckReactiveFilter(LimitAPI limitAPI, public QuotaCheckReactiveFilter(LimitAPI limitAPI,
PolarisRateLimiterLabelReactiveResolver labelResolver, PolarisRateLimiterLabelReactiveResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) { PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
this.limitAPI = limitAPI; this.limitAPI = limitAPI;
this.labelResolver = labelResolver; this.labelResolver = labelResolver;
this.polarisRateLimitProperties = polarisRateLimitProperties; this.polarisRateLimitProperties = polarisRateLimitProperties;
this.rateLimitRuleLabelResolver = rateLimitRuleLabelResolver;
} }
@PostConstruct @PostConstruct
@ -89,30 +96,12 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE; String localService = MetadataContext.LOCAL_SERVICE;
Map<String, String> labels = new HashMap<>(); Map<String, String> labels = getRequestLabels(exchange, localNamespace, localService);
// add build in labels
String path = exchange.getRequest().getURI().getPath();
if (StringUtils.isNotBlank(path)) {
labels.put(LABEL_METHOD, path);
}
// add custom labels
if (labelResolver != null) {
try {
Map<String, String> customLabels = labelResolver.resolve(exchange);
if (!CollectionUtils.isEmpty(customLabels)) {
labels.putAll(customLabels);
}
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e);
}
}
try { try {
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, labels, String path = exchange.getRequest().getURI().getPath();
null); QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI,
localNamespace, localService, 1, labels, path);
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
ServerHttpResponse response = exchange.getResponse(); ServerHttpResponse response = exchange.getResponse();
@ -122,6 +111,10 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
.write(rejectTips.getBytes(StandardCharsets.UTF_8)); .write(rejectTips.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer)); return response.writeWith(Mono.just(dataBuffer));
} }
// Unirate
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultOk && quotaResponse.getWaitMs() > 0) {
Thread.sleep(quotaResponse.getWaitMs());
}
} }
catch (Throwable t) { catch (Throwable t) {
// An exception occurs in the rate limiting API call, // An exception occurs in the rate limiting API call,
@ -132,4 +125,41 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
return chain.filter(exchange); return chain.filter(exchange);
} }
private Map<String, String> getRequestLabels(ServerWebExchange exchange, String localNamespace, String localService) {
Map<String, String> labels = new HashMap<>();
// add build in labels
String path = exchange.getRequest().getURI().getPath();
if (StringUtils.isNotBlank(path)) {
labels.put(LABEL_METHOD, path);
}
// add rule expression labels
Map<String, String> expressionLabels = getRuleExpressionLabels(exchange, localNamespace, localService);
labels.putAll(expressionLabels);
// add custom labels
Map<String, String> customResolvedLabels = getCustomResolvedLabels(exchange);
labels.putAll(customResolvedLabels);
return labels;
}
private Map<String, String> getCustomResolvedLabels(ServerWebExchange exchange) {
if (labelResolver != null) {
try {
return labelResolver.resolve(exchange);
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e);
}
}
return Maps.newHashMap();
}
private Map<String, String> getRuleExpressionLabels(ServerWebExchange exchange, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service);
return ExpressionLabelUtils.resolve(exchange, expressionLabels);
}
} }

@ -19,8 +19,10 @@
package com.tencent.cloud.polaris.ratelimit.filter; package com.tencent.cloud.polaris.ratelimit.filter;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
@ -29,6 +31,8 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
@ -42,7 +46,6 @@ 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.util.CollectionUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LABEL_METHOD; import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LABEL_METHOD;
@ -50,7 +53,7 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB
/** /**
* Servlet filter to check quota. * Servlet filter to check quota.
* *
* @author Haotian Zhang * @author Haotian Zhang, lepdou
*/ */
@Order(RateLimitConstant.FILTER_ORDER) @Order(RateLimitConstant.FILTER_ORDER)
public class QuotaCheckServletFilter extends OncePerRequestFilter { public class QuotaCheckServletFilter extends OncePerRequestFilter {
@ -63,14 +66,18 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
private final PolarisRateLimitProperties polarisRateLimitProperties; private final PolarisRateLimitProperties polarisRateLimitProperties;
private final RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
private String rejectTips; private String rejectTips;
public QuotaCheckServletFilter(LimitAPI limitAPI, public QuotaCheckServletFilter(LimitAPI limitAPI,
PolarisRateLimiterLabelServletResolver labelResolver, PolarisRateLimiterLabelServletResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) { PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
this.limitAPI = limitAPI; this.limitAPI = limitAPI;
this.labelResolver = labelResolver; this.labelResolver = labelResolver;
this.polarisRateLimitProperties = polarisRateLimitProperties; this.polarisRateLimitProperties = polarisRateLimitProperties;
this.rateLimitRuleLabelResolver = rateLimitRuleLabelResolver;
} }
@PostConstruct @PostConstruct
@ -84,38 +91,23 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE; String localService = MetadataContext.LOCAL_SERVICE;
Map<String, String> labels = new HashMap<>(); Map<String, String> labels = getRequestLabels(request, localNamespace, localService);
// add build in labels
String path = request.getRequestURI();
if (StringUtils.isNotBlank(path)) {
labels.put(LABEL_METHOD, path);
}
// add custom labels
if (labelResolver != null) {
try {
Map<String, String> customLabels = labelResolver.resolve(request);
if (!CollectionUtils.isEmpty(customLabels)) {
labels.putAll(customLabels);
}
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e);
}
}
try { try {
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, labels, QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI,
null); localNamespace, localService, 1, labels, request.getRequestURI());
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
response.setStatus(polarisRateLimitProperties.getRejectHttpCode()); response.setStatus(polarisRateLimitProperties.getRejectHttpCode());
response.getWriter().write(rejectTips); response.getWriter().write(rejectTips);
return;
} }
else { // Unirate
filterChain.doFilter(request, response); if (quotaResponse.getCode() == QuotaResultCode.QuotaResultOk && quotaResponse.getWaitMs() > 0) {
Thread.sleep(quotaResponse.getWaitMs());
} }
filterChain.doFilter(request, response);
} }
catch (Throwable t) { catch (Throwable t) {
// An exception occurs in the rate limiting API call, // An exception occurs in the rate limiting API call,
@ -125,4 +117,41 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
} }
} }
private Map<String, String> getRequestLabels(HttpServletRequest request, String localNamespace, String localService) {
Map<String, String> labels = new HashMap<>();
// add build in labels
String path = request.getRequestURI();
if (StringUtils.isNotBlank(path)) {
labels.put(LABEL_METHOD, path);
}
// add rule expression labels
Map<String, String> expressionLabels = getRuleExpressionLabels(request, localNamespace, localService);
labels.putAll(expressionLabels);
// add custom resolved labels
Map<String, String> customLabels = getCustomResolvedLabels(request);
labels.putAll(customLabels);
return labels;
}
private Map<String, String> getCustomResolvedLabels(HttpServletRequest request) {
if (labelResolver != null) {
try {
return labelResolver.resolve(request);
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}",
labelResolver.getClass().getName(), e);
}
}
return Collections.emptyMap();
}
private Map<String, String> getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service);
return ExpressionLabelUtils.resolve(request, expressionLabels);
}
} }

@ -39,16 +39,15 @@ public final class RateLimitUtils {
} }
public static String getRejectTips( public static String getRejectTips(PolarisRateLimitProperties polarisRateLimitProperties) {
PolarisRateLimitProperties polarisRateLimitProperties) {
String tips = polarisRateLimitProperties.getRejectRequestTips(); String tips = polarisRateLimitProperties.getRejectRequestTips();
if (!StringUtils.isEmpty(tips)) { if (StringUtils.hasText(tips)) {
return tips; return tips;
} }
String rejectFilePath = polarisRateLimitProperties.getRejectRequestTipsFilePath(); String rejectFilePath = polarisRateLimitProperties.getRejectRequestTipsFilePath();
if (!StringUtils.isEmpty(rejectFilePath)) { if (StringUtils.hasText(rejectFilePath)) {
try { try {
tips = ResourceFileUtils.readFile(rejectFilePath); tips = ResourceFileUtils.readFile(rejectFilePath);
} }
@ -58,7 +57,7 @@ public final class RateLimitUtils {
} }
} }
if (!StringUtils.isEmpty(tips)) { if (StringUtils.hasText(tips)) {
return tips; return tips;
} }

@ -23,6 +23,12 @@
"type": "java.lang.Integer", "type": "java.lang.Integer",
"defaultValue": "429", "defaultValue": "429",
"description": "Custom http code when reject request." "description": "Custom http code when reject request."
},
{
"name": "spring.cloud.polaris.ratelimit.maxQueuingTime",
"type": "java.lang.Long",
"defaultValue": "1000",
"description": "Max queuing time when using unirate."
} }
] ]
} }

@ -1,2 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.ratelimit.config.RateLimitConfiguration com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitBootstrapConfiguration

@ -0,0 +1,101 @@
/*
* 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.ratelimit;
import java.util.Set;
import com.google.protobuf.StringValue;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RateLimitProto;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link RateLimitRuleLabelResolver}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class RateLimitRuleLabelResolverTest {
private ServiceRuleManager serviceRuleManager;
private RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
@Before
public void setUp() {
serviceRuleManager = mock(ServiceRuleManager.class);
when(serviceRuleManager.getServiceRateLimitRule(any(), anyString())).thenAnswer(invocationOnMock -> {
String serviceName = invocationOnMock.getArgument(1).toString();
if (serviceName.equals("TestApp1")) {
return null;
}
else if (serviceName.equals("TestApp2")) {
return RateLimitProto.RateLimit.newBuilder().build();
}
else if (serviceName.equals("TestApp3")) {
RateLimitProto.Rule rule = RateLimitProto.Rule.newBuilder().build();
return RateLimitProto.RateLimit.newBuilder().addRules(rule).build();
}
else {
ModelProto.MatchString matchString = ModelProto.MatchString.newBuilder()
.setType(ModelProto.MatchString.MatchStringType.EXACT)
.setValue(StringValue.of("value"))
.setValueType(ModelProto.MatchString.ValueType.TEXT).build();
RateLimitProto.Rule rule = RateLimitProto.Rule.newBuilder()
.putLabels("${http.method}", matchString).build();
return RateLimitProto.RateLimit.newBuilder().addRules(rule).build();
}
});
rateLimitRuleLabelResolver = new RateLimitRuleLabelResolver(serviceRuleManager);
}
@Test
public void testGetExpressionLabelKeys() {
// rateLimitRule == null
String serviceName = "TestApp1";
Set<String> labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isEmpty();
// CollectionUtils.isEmpty(rules)
serviceName = "TestApp2";
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isEmpty();
// CollectionUtils.isEmpty(labels)
serviceName = "TestApp3";
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isEmpty();
// Has labels
serviceName = "TestApp4";
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isNotEmpty();
assertThat(labelKeys).contains("${http.method}");
}
}

@ -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.polaris.ratelimit.config;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisRateLimitBootstrapConfiguration}.
*
* @author Haotian Zhang
*/
public class PolarisRateLimitBootstrapConfigurationTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(PolarisRateLimitBootstrapConfiguration.class))
.withPropertyValues("spring.cloud.polaris.ratelimit.enabled=true");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(PolarisRateLimitProperties.class);
assertThat(context).hasSingleBean(PolarisRateLimitBootstrapConfiguration.RateLimitConfigModifier.class);
});
}
}

@ -0,0 +1,99 @@
/*
* 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.ratelimit.config;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisRateLimitConfiguration}.
*
* @author Haotian Zhang
*/
public class PolarisRateLimitConfigurationTest {
private ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner();
private WebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner();
private ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = new ReactiveWebApplicationContextRunner();
@Test
public void testNoWebApplication() {
this.applicationContextRunner
.withConfiguration(AutoConfigurations.of(
PolarisContextAutoConfiguration.class,
PolarisRateLimitProperties.class,
PolarisRateLimitConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(LimitAPI.class);
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class);
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class);
});
}
@Test
public void testServletWebApplication() {
this.webApplicationContextRunner
.withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class,
PolarisRateLimitProperties.class,
PolarisRateLimitConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(LimitAPI.class);
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
assertThat(context).hasSingleBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).hasSingleBean(QuotaCheckServletFilter.class);
assertThat(context).hasSingleBean(FilterRegistrationBean.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class);
});
}
@Test
public void testReactiveWebApplication() {
this.reactiveWebApplicationContextRunner
.withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class,
PolarisRateLimitProperties.class,
PolarisRateLimitConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(LimitAPI.class);
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class);
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
assertThat(context).hasSingleBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
assertThat(context).hasSingleBean(QuotaCheckReactiveFilter.class);
});
}
}

@ -0,0 +1,59 @@
/*
* 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.ratelimit.config;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisRateLimitProperties}.
*
* @author Haotian Zhang
*/
public class PolarisRateLimitPropertiesTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PolarisRateLimitPropertiesAutoConfiguration.class, PolarisRateLimitProperties.class))
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectRequestTips=xxx")
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectRequestTipsFilePath=/index.html")
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectHttpCode=419")
.withPropertyValues("spring.cloud.polaris.ratelimit.maxQueuingTime=500");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
PolarisRateLimitProperties polarisRateLimitProperties = context.getBean(PolarisRateLimitProperties.class);
assertThat(polarisRateLimitProperties.getRejectRequestTips()).isEqualTo("xxx");
assertThat(polarisRateLimitProperties.getRejectRequestTipsFilePath()).isEqualTo("/index.html");
assertThat(polarisRateLimitProperties.getRejectHttpCode()).isEqualTo(419);
assertThat(polarisRateLimitProperties.getMaxQueuingTime()).isEqualTo(500L);
});
}
@Configuration
@EnableAutoConfiguration
static class PolarisRateLimitPropertiesAutoConfiguration {
}
}

@ -0,0 +1,223 @@
/*
* 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.ratelimit.filter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* Test for {@link QuotaCheckReactiveFilter}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
@SpringBootTest(classes = QuotaCheckReactiveFilterTest.TestApplication.class, properties = {
"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"
})
public class QuotaCheckReactiveFilterTest {
private PolarisRateLimiterLabelReactiveResolver labelResolver = exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver");
private QuotaCheckReactiveFilter quotaCheckReactiveFilter;
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
@BeforeClass
public static void beforeClass() {
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())).thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
}
@AfterClass
public static void afterClass() {
mockedApplicationContextAwareUtils.close();
expressionLabelUtilsMockedStatic.close();
}
@Before
public void setUp() {
MetadataContext.LOCAL_NAMESPACE = "TEST";
LimitAPI limitAPI = mock(LimitAPI.class);
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
if (serviceName.equals("TestApp1")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp2")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
}
else {
return new QuotaResponse(new QuotaResult(null, 0, null));
}
});
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
polarisRateLimitProperties.setRejectHttpCode(419);
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.EMPTY_SET);
this.quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
}
@Test
public void testGetOrder() {
assertThat(this.quotaCheckReactiveFilter.getOrder()).isEqualTo(RateLimitConstant.FILTER_ORDER);
}
@Test
public void testInit() {
quotaCheckReactiveFilter.init();
try {
Field rejectTips = QuotaCheckReactiveFilter.class.getDeclaredField("rejectTips");
rejectTips.setAccessible(true);
assertThat(rejectTips.get(quotaCheckReactiveFilter)).isEqualTo("RejectRequestTips");
}
catch (NoSuchFieldException | IllegalAccessException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testGetRuleExpressionLabels() {
try {
Method getCustomResolvedLabels = QuotaCheckReactiveFilter.class.getDeclaredMethod("getCustomResolvedLabels", ServerWebExchange.class);
getCustomResolvedLabels.setAccessible(true);
// Mock request
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/test").build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
// labelResolver != null
Map<String, String> result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
assertThat(result.size()).isEqualTo(1);
assertThat(result.get("ReactiveResolver")).isEqualTo("ReactiveResolver");
// throw exception
PolarisRateLimiterLabelReactiveResolver exceptionLabelResolver = new PolarisRateLimiterLabelReactiveResolver() {
@Override
public Map<String, String> resolve(ServerWebExchange exchange) {
throw new RuntimeException("Mock exception.");
}
};
quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(null, exceptionLabelResolver, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
assertThat(result.size()).isEqualTo(0);
// labelResolver == null
quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(null, null, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
assertThat(result.size()).isEqualTo(0);
getCustomResolvedLabels.setAccessible(false);
}
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testFilter() {
// Create mock WebFilterChain
WebFilterChain webFilterChain = serverWebExchange -> Mono.empty();
// Mock request
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/test").build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
quotaCheckReactiveFilter.init();
// Pass
MetadataContext.LOCAL_SERVICE = "TestApp1";
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
// Unirate waiting 1000ms
MetadataContext.LOCAL_SERVICE = "TestApp2";
long startTimestamp = System.currentTimeMillis();
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L);
// Rate limited
MetadataContext.LOCAL_SERVICE = "TestApp3";
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
ServerHttpResponse response = exchange.getResponse();
assertThat(response.getRawStatusCode()).isEqualTo(419);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE);
// Exception
MetadataContext.LOCAL_SERVICE = "TestApp4";
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,220 @@
/*
* 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.ratelimit.filter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* Test for {@link QuotaCheckServletFilter}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
@SpringBootTest(classes = QuotaCheckServletFilterTest.TestApplication.class, properties = {
"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"
})
public class QuotaCheckServletFilterTest {
private PolarisRateLimiterLabelServletResolver labelResolver = exchange -> Collections.singletonMap("ServletResolver", "ServletResolver");
private QuotaCheckServletFilter quotaCheckServletFilter;
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
@BeforeClass
public static void beforeClass() {
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())).thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
}
@AfterClass
public static void afterClass() throws Exception {
mockedApplicationContextAwareUtils.close();
expressionLabelUtilsMockedStatic.close();
}
@Before
public void setUp() {
MetadataContext.LOCAL_NAMESPACE = "TEST";
LimitAPI limitAPI = mock(LimitAPI.class);
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
if (serviceName.equals("TestApp1")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp2")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
}
else {
return new QuotaResponse(new QuotaResult(null, 0, null));
}
});
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
polarisRateLimitProperties.setRejectHttpCode(419);
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.EMPTY_SET);
this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
}
@Test
public void testInit() {
quotaCheckServletFilter.init();
try {
Field rejectTips = QuotaCheckServletFilter.class.getDeclaredField("rejectTips");
rejectTips.setAccessible(true);
assertThat(rejectTips.get(quotaCheckServletFilter)).isEqualTo("RejectRequestTips");
}
catch (NoSuchFieldException | IllegalAccessException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testGetRuleExpressionLabels() {
try {
Method getCustomResolvedLabels = QuotaCheckServletFilter.class.getDeclaredMethod("getCustomResolvedLabels", HttpServletRequest.class);
getCustomResolvedLabels.setAccessible(true);
// Mock request
MockHttpServletRequest request = new MockHttpServletRequest();
// labelResolver != null
Map<String, String> result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
assertThat(result.size()).isEqualTo(1);
assertThat(result.get("ServletResolver")).isEqualTo("ServletResolver");
// throw exception
PolarisRateLimiterLabelServletResolver exceptionLabelResolver = request1 -> {
throw new RuntimeException("Mock exception.");
};
quotaCheckServletFilter = new QuotaCheckServletFilter(null, exceptionLabelResolver, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
assertThat(result.size()).isEqualTo(0);
// labelResolver == null
quotaCheckServletFilter = new QuotaCheckServletFilter(null, null, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
assertThat(result.size()).isEqualTo(0);
getCustomResolvedLabels.setAccessible(false);
}
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testDoFilterInternal() {
// Create mock FilterChain
FilterChain filterChain = (servletRequest, servletResponse) -> {
};
// Mock request
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
quotaCheckServletFilter.init();
try {
// Pass
MetadataContext.LOCAL_SERVICE = "TestApp1";
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
// Unirate waiting 1000ms
MetadataContext.LOCAL_SERVICE = "TestApp2";
long startTimestamp = System.currentTimeMillis();
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L);
// Rate limited
MetadataContext.LOCAL_SERVICE = "TestApp3";
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
assertThat(response.getStatus()).isEqualTo(419);
assertThat(response.getContentAsString()).isEqualTo("RejectRequestTips");
// Exception
MetadataContext.LOCAL_SERVICE = "TestApp4";
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
}
catch (ServletException | IOException e) {
fail("Exception encountered.", e);
}
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,95 @@
/*
* 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.ratelimit.utils;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link QuotaCheckUtils}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class QuotaCheckUtilsTest {
private LimitAPI limitAPI;
@Before
public void setUp() {
limitAPI = mock(LimitAPI.class);
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
if (serviceName.equals("TestApp1")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp2")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
}
else {
throw new RuntimeException("Mock exception.");
}
});
}
@Test
public void testGetQuota() {
// Pass
String serviceName = "TestApp1";
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk");
// Unirate waiting 1000ms
serviceName = "TestApp2";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(1000);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk");
// Rate limited
serviceName = "TestApp3";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultLimited);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultLimited");
// Exception
serviceName = "TestApp4";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("get quota failed");
}
}

@ -0,0 +1,75 @@
/*
* 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.ratelimit.utils;
import java.io.IOException;
import com.tencent.cloud.common.util.ResourceFileUtils;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.QUOTA_LIMITED_INFO;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* Test for {@link RateLimitUtils}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class RateLimitUtilsTest {
@BeforeClass
public static void beforeClass() throws IOException {
mockStatic(ResourceFileUtils.class);
when(ResourceFileUtils.readFile(anyString())).thenAnswer(invocation -> {
String rejectFilePath = invocation.getArgument(0).toString();
if (rejectFilePath.equals("exception.html")) {
throw new IOException("Mock exceptions");
}
else {
return "RejectRequestTips";
}
});
}
@Test
public void testGetRejectTips() {
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
// RejectRequestTips
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo("RejectRequestTips");
// RejectRequestTipsFilePath
polarisRateLimitProperties.setRejectRequestTips(null);
polarisRateLimitProperties.setRejectRequestTipsFilePath("reject-tips.html");
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo("RejectRequestTips");
// RejectRequestTipsFilePath with Exception
polarisRateLimitProperties.setRejectRequestTips(null);
polarisRateLimitProperties.setRejectRequestTipsFilePath("exception.html");
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo(QUOTA_LIMITED_INFO);
}
}

@ -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-starter-tencent-metadata-transfer</artifactId>
</dependency>
<!-- Spring Cloud Tencent dependencies end --> <!-- Spring Cloud Tencent dependencies end -->
<!-- Polaris dependencies start --> <!-- Polaris dependencies start -->
@ -26,7 +30,51 @@
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.polaris</groupId>
<artifactId>router-rule</artifactId> <artifactId>router-rule</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-metadata</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId>
</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>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

@ -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.polaris.router;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.CollectionUtils;
/**
* the context for router.
*
*@author lepdou 2022-05-17
*/
public class PolarisRouterContext {
/**
* the label for rule router.
*/
public static final String RULE_ROUTER_LABELS = "ruleRouter";
/**
* transitive labels.
*/
public static final String TRANSITIVE_LABELS = "transitive";
private Map<String, Map<String, String>> labels;
public Map<String, String> getLabels(String labelType) {
if (CollectionUtils.isEmpty(labels)) {
return Collections.emptyMap();
}
Map<String, String> subLabels = labels.get(labelType);
if (CollectionUtils.isEmpty(subLabels)) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(subLabels);
}
public void setLabels(String labelType, Map<String, String> subLabels) {
if (this.labels == null) {
this.labels = new HashMap<>();
}
labels.put(labelType, subLabels);
}
}

@ -0,0 +1,210 @@
/*
* 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.router;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.pojo.PolarisServiceInstance;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils;
import com.tencent.cloud.polaris.router.config.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerRequest;
import com.tencent.polaris.api.exception.ErrorCode;
import com.tencent.polaris.api.exception.PolarisException;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.plugins.router.metadata.MetadataRouter;
import com.tencent.polaris.plugins.router.nearby.NearbyRouter;
import com.tencent.polaris.plugins.router.rule.RuleBasedRouter;
import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultRequestContext;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.loadbalancer.core.DelegatingServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import org.springframework.util.CollectionUtils;
/**
* Service routing entrance.
*
* Rule routing needs to rely on request parameters for server filtering.
* The interface cannot obtain the context object of the request granularity,
* so the routing capability cannot be achieved through ServerListFilter.
*
* And {@link PolarisRouterServiceInstanceListSupplier#get(Request)} provides the ability to pass in http headers,
* so routing capabilities are implemented through IRule.
*
* @author Haotian Zhang, lepdou
*/
public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisRouterServiceInstanceListSupplier.class);
private final PolarisNearByRouterProperties polarisNearByRouterProperties;
private final PolarisMetadataRouterProperties polarisMetadataRouterProperties;
private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
private final RouterAPI routerAPI;
public PolarisRouterServiceInstanceListSupplier(ServiceInstanceListSupplier delegate,
RouterAPI routerAPI,
PolarisNearByRouterProperties polarisNearByRouterProperties,
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
super(delegate);
this.routerAPI = routerAPI;
this.polarisNearByRouterProperties = polarisNearByRouterProperties;
this.polarisMetadataRouterProperties = polarisMetadataRouterProperties;
this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties;
}
@Override
public Flux<List<ServiceInstance>> get() {
throw new PolarisException(ErrorCode.INTERNAL_ERROR, "Unsupported method.");
}
@Override
public Flux<List<ServiceInstance>> get(Request request) {
// 1. get all servers
Flux<List<ServiceInstance>> allServers = getDelegate().get();
// 2. filter by router
DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();
PolarisRouterContext key = null;
if (requestContext instanceof RequestDataContext) {
key = buildRouterContext(((RequestDataContext) requestContext).getClientRequest().getHeaders());
}
else if (requestContext.getClientRequest() instanceof PolarisLoadBalancerRequest) {
key = buildRouterContext(((PolarisLoadBalancerRequest<?>) requestContext.getClientRequest()).getRequest()
.getHeaders());
}
return doRouter(allServers, key);
}
//set method to public for unit test
PolarisRouterContext buildRouterContext(HttpHeaders headers) {
Collection<String> labelHeaderValues = headers.get(RouterConstants.ROUTER_LABEL_HEADER);
if (CollectionUtils.isEmpty(labelHeaderValues)) {
return null;
}
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE));
labelHeaderValues.forEach(labelHeaderValue -> {
try {
Map<String, String> labels = JacksonUtils.deserialize2Map(URLDecoder.decode(labelHeaderValue, "UTF-8"));
if (!CollectionUtils.isEmpty(labels)) {
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels);
}
}
catch (UnsupportedEncodingException e) {
LOGGER.error("Decode header[{}] failed.", labelHeaderValue, e);
throw new RuntimeException(e);
}
});
return routerContext;
}
Flux<List<ServiceInstance>> doRouter(Flux<List<ServiceInstance>> allServers, PolarisRouterContext key) {
ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers);
// filter instance by routers
ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, key);
ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest);
List<ServiceInstance> filteredInstances = new ArrayList<>();
ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances();
for (Instance instance : filteredServiceInstances.getInstances()) {
filteredInstances.add(new PolarisServiceInstance(instance));
}
return Flux.fromIterable(Collections.singletonList(filteredInstances));
}
ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, PolarisRouterContext key) {
ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest();
processRoutersRequest.setDstInstances(serviceInstances);
// metadata router
if (polarisMetadataRouterProperties.isEnabled()) {
Map<String, String> transitiveLabels = getRouterLabels(key, PolarisRouterContext.TRANSITIVE_LABELS);
processRoutersRequest.putRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, transitiveLabels);
}
// nearby router
if (polarisNearByRouterProperties.isEnabled()) {
Map<String, String> nearbyRouterMetadata = new HashMap<>();
nearbyRouterMetadata.put(NearbyRouter.ROUTER_ENABLED, "true");
processRoutersRequest.putRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY, nearbyRouterMetadata);
}
// rule based router
// set dynamic switch for rule based router
boolean ruleBasedRouterEnabled = polarisRuleBasedRouterProperties.isEnabled();
Map<String, String> ruleRouterMetadata = new HashMap<>();
ruleRouterMetadata.put(RuleBasedRouter.ROUTER_ENABLED, String.valueOf(ruleBasedRouterEnabled));
processRoutersRequest.putRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED, ruleRouterMetadata);
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setNamespace(MetadataContext.LOCAL_NAMESPACE);
serviceInfo.setService(MetadataContext.LOCAL_SERVICE);
if (ruleBasedRouterEnabled) {
Map<String, String> ruleRouterLabels = getRouterLabels(key, PolarisRouterContext.RULE_ROUTER_LABELS);
// The label information that the rule based routing depends on
// is placed in the metadata of the source service for transmission.
// Later, can consider putting it in routerMetadata like other routers.
serviceInfo.setMetadata(ruleRouterLabels);
}
processRoutersRequest.setSourceService(serviceInfo);
return processRoutersRequest;
}
private Map<String, String> getRouterLabels(PolarisRouterContext key, String type) {
if (key != null) {
return key.getLabels(type);
}
return Collections.emptyMap();
}
}

@ -13,11 +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.router;
/** /**
* Package info of router. * Router constants.
* *
* @author Haotian Zhang *@author lepdou 2022-05-17
*/ */
package com.tencent.cloud.polaris.router; public class RouterConstants {
/**
* the header of router label.
*/
public static final String ROUTER_LABEL_HEADER = "internal-router-label";
}

@ -0,0 +1,75 @@
/*
* 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.router;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RoutingProto;
import org.springframework.util.CollectionUtils;
/**
* Resolve label expressions from routing rules.
* @author lepdou 2022-05-19
*/
public class RouterRuleLabelResolver {
private final ServiceRuleManager serviceRuleManager;
public RouterRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
this.serviceRuleManager = serviceRuleManager;
}
public Set<String> getExpressionLabelKeys(String namespace, String sourceService, String dstService) {
List<RoutingProto.Route> rules = serviceRuleManager.getServiceRouterRule(namespace, sourceService, dstService);
if (CollectionUtils.isEmpty(rules)) {
return Collections.emptySet();
}
Set<String> expressionLabels = new HashSet<>();
for (RoutingProto.Route rule : rules) {
List<RoutingProto.Source> sources = rule.getSourcesList();
if (CollectionUtils.isEmpty(sources)) {
continue;
}
for (RoutingProto.Source source : sources) {
Map<String, ModelProto.MatchString> labels = source.getMetadataMap();
if (CollectionUtils.isEmpty(labels)) {
continue;
}
for (String labelKey : labels.keySet()) {
if (ExpressionLabelUtils.isExpressionLabel(labelKey)) {
expressionLabels.add(labelKey);
}
}
}
}
return expressionLabels;
}
}

@ -0,0 +1,93 @@
/*
* 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.router.config;
import com.tencent.cloud.polaris.router.PolarisRouterServiceInstanceListSupplier;
import com.tencent.polaris.router.api.core.RouterAPI;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
/**
* Auto configuration for ribbon components.
* @author lepdou 2022-05-17
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerConfiguration {
/**
* Order of reactive discovery service instance supplier.
*/
private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
@Configuration
@ConditionalOnReactiveDiscoveryEnabled
@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER)
static class PolarisReactiveSupportConfiguration {
@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context,
RouterAPI routerAPI,
PolarisNearByRouterProperties polarisNearByRouterProperties,
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
return new PolarisRouterServiceInstanceListSupplier(
ServiceInstanceListSupplier.builder().withDiscoveryClient().build(context),
routerAPI,
polarisNearByRouterProperties,
polarisMetadataRouterProperties,
polarisRuleBasedRouterProperties);
}
}
@Configuration
@ConditionalOnBlockingDiscoveryEnabled
@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER + 1)
static class PolarisBlockingSupportConfiguration {
@Bean
@ConditionalOnBean(DiscoveryClient.class)
public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context,
RouterAPI routerAPI,
PolarisNearByRouterProperties polarisNearByRouterProperties,
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
return new PolarisRouterServiceInstanceListSupplier(
ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().build(context),
routerAPI,
polarisNearByRouterProperties,
polarisMetadataRouterProperties,
polarisRuleBasedRouterProperties);
}
}
}

@ -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.polaris.router.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* the configuration for metadata router.
* @author lepdou 2022-05-23
*/
@ConfigurationProperties(prefix = "spring.cloud.polaris.router.metadata-router")
public class PolarisMetadataRouterProperties {
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return "PolarisMetadataRouterProperties{" +
"enabled=" + enabled +
'}';
}
}

@ -0,0 +1,47 @@
/*
* 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.router.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* the configuration for nearby router.
*
* @author lepdou 2022-05-23
*/
@ConfigurationProperties(prefix = "spring.cloud.polaris.router.nearby-router")
public class PolarisNearByRouterProperties {
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return "PolarisNearByRouterProperties{" +
"enabled=" + enabled +
'}';
}
}

@ -0,0 +1,48 @@
/*
* 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.router.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* the configuration for rule based router.
*
* @author lepdou 2022-05-23
*/
@ConfigurationProperties(prefix = "spring.cloud.polaris.router.rule-router")
public class PolarisRuleBasedRouterProperties {
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return "PolarisNearByRouterProperties{" +
"enabled=" + enabled +
'}';
}
}

@ -0,0 +1,66 @@
/*
* 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.router.config;
import java.util.List;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.feign.RouterLabelFeignInterceptor;
import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerBeanPostProcessor;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.lang.Nullable;
import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
/**
* router module auto configuration.
*
*@author lepdou 2022-05-11
*/
@Configuration
@LoadBalancerClients(defaultConfiguration = LoadBalancerConfiguration.class)
@Import({PolarisNearByRouterProperties.class, PolarisMetadataRouterProperties.class, PolarisRuleBasedRouterProperties.class})
public class RouterAutoConfiguration {
@Bean
public RouterLabelFeignInterceptor routerLabelInterceptor(@Nullable List<RouterLabelResolver> routerLabelResolvers,
MetadataLocalProperties metadataLocalProperties,
RouterRuleLabelResolver routerRuleLabelResolver) {
return new RouterLabelFeignInterceptor(routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver);
}
@Bean
@Order(HIGHEST_PRECEDENCE)
public PolarisLoadBalancerBeanPostProcessor polarisLoadBalancerBeanPostProcessor() {
return new PolarisLoadBalancerBeanPostProcessor();
}
@Bean
public RouterRuleLabelResolver routerRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
return new RouterRuleLabelResolver(serviceRuleManager);
}
}

@ -0,0 +1,83 @@
/*
* 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.router.feign;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import feign.RequestTemplate;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
/**
* Resolve rule expression label from feign request.
* @author lepdou 2022-05-20
*/
public class FeignExpressionLabelUtils {
public static Map<String, String> resolve(RequestTemplate request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) {
String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, getHeaderValue(request, headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) {
String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(request, queryKey));
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) {
labels.put(labelKey, request.method());
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) {
URI uri = URI.create(request.request().url());
labels.put(labelKey, uri.getPath());
}
}
return labels;
}
public static String getHeaderValue(RequestTemplate request, String key) {
Map<String, Collection<String>> headers = request.headers();
return ExpressionLabelUtils.getFirstValue(headers, key);
}
public static String getQueryValue(RequestTemplate request, String key) {
return ExpressionLabelUtils.getFirstValue(request.queries(), key);
}
}

@ -0,0 +1,132 @@
/*
* 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.router.feign;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.util.CollectionUtils;
/**
* Resolver labels from request.
*
*@author lepdou 2022-05-12
*/
public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelFeignInterceptor.class);
private final List<RouterLabelResolver> routerLabelResolvers;
private final MetadataLocalProperties metadataLocalProperties;
private final RouterRuleLabelResolver routerRuleLabelResolver;
public RouterLabelFeignInterceptor(List<RouterLabelResolver> routerLabelResolvers,
MetadataLocalProperties metadataLocalProperties,
RouterRuleLabelResolver routerRuleLabelResolver) {
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder));
this.routerLabelResolvers = routerLabelResolvers;
}
else {
this.routerLabelResolvers = null;
}
this.metadataLocalProperties = metadataLocalProperties;
this.routerRuleLabelResolver = routerRuleLabelResolver;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void apply(RequestTemplate requestTemplate) {
// local service labels
Map<String, String> labels = new HashMap<>(metadataLocalProperties.getContent());
// labels from rule expression
String peerServiceName = requestTemplate.feignTarget().name();
Map<String, String> ruleExpressionLabels = getRuleExpressionLabels(requestTemplate, peerServiceName);
labels.putAll(ruleExpressionLabels);
// labels from request
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.forEach(resolver -> {
try {
Map<String, String> customResolvedLabels = resolver.resolve(requestTemplate);
if (!CollectionUtils.isEmpty(customResolvedLabels)) {
labels.putAll(customResolvedLabels);
}
}
catch (Throwable t) {
LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t);
}
});
}
// labels from downstream
Map<String, String> transitiveLabels = MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
labels.putAll(transitiveLabels);
// pass label by header
if (labels.size() == 0) {
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER);
return;
}
try {
String headerMetadataStr = URLEncoder.encode(JacksonUtils.serialize2Json(labels), "UTF-8");
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, headerMetadataStr);
}
catch (UnsupportedEncodingException e) {
LOGGER.error("Set header failed.", e);
throw new RuntimeException(e);
}
}
private Map<String, String> getRuleExpressionLabels(RequestTemplate requestTemplate, String peerService) {
Set<String> labelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, peerService);
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
return FeignExpressionLabelUtils.resolve(requestTemplate, labelKeys);
}
}

@ -0,0 +1,65 @@
/*
* 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.router.resttemplate;
import java.util.List;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.BeanFactoryUtils;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
/**
* Replace LoadBalancerInterceptor with PolarisLoadBalancerInterceptor.
* PolarisLoadBalancerInterceptor can pass routing context information.
*
*@author lepdou 2022-05-18
*/
public class PolarisLoadBalancerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private BeanFactory factory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.factory = beanFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof LoadBalancerInterceptor) {
LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class);
LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class);
List<RouterLabelResolver> routerLabelResolvers = BeanFactoryUtils.getBeans(factory, RouterLabelResolver.class);
MetadataLocalProperties metadataLocalProperties = this.factory.getBean(MetadataLocalProperties.class);
RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class);
return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory,
routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver);
}
return bean;
}
}

@ -0,0 +1,156 @@
/*
* 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.router.resttemplate;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor capabilities.
* Parses the label from the request and puts it into the RouterContext for routing.
*
*@author lepdou 2022-05-18
*/
public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisLoadBalancerInterceptor.class);
private final LoadBalancerClient loadBalancer;
private final LoadBalancerRequestFactory requestFactory;
private final List<RouterLabelResolver> routerLabelResolvers;
private final MetadataLocalProperties metadataLocalProperties;
private final RouterRuleLabelResolver routerRuleLabelResolver;
public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory,
List<RouterLabelResolver> routerLabelResolvers,
MetadataLocalProperties metadataLocalProperties,
RouterRuleLabelResolver routerRuleLabelResolver) {
super(loadBalancer, requestFactory);
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
this.metadataLocalProperties = metadataLocalProperties;
this.routerRuleLabelResolver = routerRuleLabelResolver;
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder));
this.routerLabelResolvers = routerLabelResolvers;
}
else {
this.routerLabelResolvers = null;
}
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String peerServiceName = originalUri.getHost();
Assert.state(peerServiceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
setLabelsToHeaders(request, body, peerServiceName);
return this.loadBalancer.execute(peerServiceName,
new PolarisLoadBalancerRequest<>(request, this.requestFactory.createRequest(request, body, execution)));
}
void setLabelsToHeaders(HttpRequest request, byte[] body, String peerServiceName) {
// local service labels
Map<String, String> labels = new HashMap<>(metadataLocalProperties.getContent());
// labels from rule expression
Map<String, String> ruleExpressionLabels = getExpressionLabels(request, peerServiceName);
if (!CollectionUtils.isEmpty(ruleExpressionLabels)) {
labels.putAll(ruleExpressionLabels);
}
// labels from request
if (!CollectionUtils.isEmpty(routerLabelResolvers)) {
routerLabelResolvers.forEach(resolver -> {
try {
Map<String, String> customResolvedLabels = resolver.resolve(request, body);
if (!CollectionUtils.isEmpty(customResolvedLabels)) {
labels.putAll(customResolvedLabels);
}
}
catch (Throwable t) {
LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t);
}
});
}
// labels from downstream
Map<String, String> transitiveLabels = MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
labels.putAll(transitiveLabels);
// pass label by header
if (labels.size() == 0) {
request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, null);
return;
}
try {
String headerMetadataStr = URLEncoder.encode(JacksonUtils.serialize2Json(labels), "UTF-8");
request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, headerMetadataStr);
}
catch (UnsupportedEncodingException e) {
LOGGER.error("Set header failed.", e);
throw new RuntimeException(e);
}
}
private Map<String, String> getExpressionLabels(HttpRequest request, String peerServiceName) {
Set<String> labelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, peerServiceName);
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
return ExpressionLabelUtils.resolve(request, labelKeys);
}
}

@ -0,0 +1,52 @@
/*
* 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.router.resttemplate;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.http.HttpRequest;
/**
* Wrapper of {@link LoadBalancerRequest}.
*
* @author Haotian Zhang
*/
public class PolarisLoadBalancerRequest<T> implements LoadBalancerRequest<T> {
private HttpRequest request;
private LoadBalancerRequest<T> delegate;
public PolarisLoadBalancerRequest(HttpRequest request, LoadBalancerRequest<T> delegate) {
this.request = request;
this.delegate = delegate;
}
@Override
public T apply(ServiceInstance instance) throws Exception {
return delegate.apply(instance);
}
public HttpRequest getRequest() {
return request;
}
public LoadBalancerRequest<T> getDelegate() {
return delegate;
}
}

@ -0,0 +1,49 @@
/*
* 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.router.spi;
import java.util.Map;
import feign.RequestTemplate;
import org.springframework.core.Ordered;
import org.springframework.http.HttpRequest;
/**
* The spi for resolving labels from request.
*
* @author lepdou 2022-05-11
*/
public interface RouterLabelResolver extends Ordered {
/**
* resolve labels from feign request.
* @param requestTemplate the feign request.
* @return resolved labels
*/
Map<String, String> resolve(RequestTemplate requestTemplate);
/**
* resolve labels from rest template request.
* @param request the rest template request.
* @param body the rest template request body.
* @return resolved labels
*/
Map<String, String> resolve(HttpRequest request, byte[] body);
}

@ -0,0 +1,22 @@
{
"properties": [
{
"name": "spring.cloud.polaris.router.metadata-router.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for metadata router."
},
{
"name": "spring.cloud.polaris.router.nearby-router.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for near by router."
},
{
"name": "spring.cloud.polaris.router.rule-router.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for rule based router."
}
]
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.router.config.RouterAutoConfiguration

@ -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.polaris.router;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
/**
* test for {@link PolarisRouterContext}
*
*@author lepdou 2022-05-26
*/
public class PolarisRouterContextTest {
@Test
public void testNormalGetterSetter() {
Map<String, String> labels = new HashMap<>();
labels.put("k1", "v1");
labels.put("k2", "v2");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels);
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size());
Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size());
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k1"));
Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k2"));
Assert.assertNull(routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k3"));
}
@Test
public void testSetNull() {
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, null);
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size());
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size());
}
@Test
public void testGetEmptyRouterContext() {
PolarisRouterContext routerContext = new PolarisRouterContext();
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size());
Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size());
}
}

@ -0,0 +1,248 @@
/*
* 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.router;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
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.cloud.polaris.router.config.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.PolarisRuleBasedRouterProperties;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.plugins.router.metadata.MetadataRouter;
import com.tencent.polaris.plugins.router.nearby.NearbyRouter;
import com.tencent.polaris.plugins.router.rule.RuleBasedRouter;
import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
import org.junit.Assert;
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 org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* test for {@link PolarisRouterServiceInstanceListSupplier}
*@author lepdou 2022-05-26
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisRouterServiceInstanceListSupplierTest {
private static AtomicBoolean initTransitiveMetadata = new AtomicBoolean(false);
@Mock
private ServiceInstanceListSupplier delegate;
@Mock
private PolarisLoadBalancerProperties polarisLoadBalancerProperties;
@Mock
private PolarisNearByRouterProperties polarisNearByRouterProperties;
@Mock
private PolarisMetadataRouterProperties polarisMetadataRouterProperties;
@Mock
private PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
@Mock
private RouterAPI routerAPI;
private String testNamespace = "testNamespace";
private String testCallerService = "testCallerService";
private String testCalleeService = "testCalleeService";
@Test
public void testBuildMetadataRouteRequest() {
when(polarisMetadataRouterProperties.isEnabled()).thenReturn(true);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(testCallerService);
setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA);
Assert.assertEquals(1, routerMetadata.size());
Assert.assertEquals(0, request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY).size());
Assert.assertEquals(1, request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED).size());
Assert.assertEquals("false", request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED)
.get(RuleBasedRouter.ROUTER_ENABLED));
}
}
@Test
public void testBuildNearbyRouteRequest() {
when(polarisNearByRouterProperties.isEnabled()).thenReturn(true);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(testCallerService);
setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY);
Assert.assertEquals(0, request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA).size());
Assert.assertEquals(1, routerMetadata.size());
Assert.assertEquals("true", routerMetadata.get(NearbyRouter.ROUTER_ENABLED));
Assert.assertEquals(1, request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED).size());
Assert.assertEquals("false", request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED)
.get(RuleBasedRouter.ROUTER_ENABLED));
}
}
@Test
public void testBuildRuleBasedRouteRequest() {
when(polarisRuleBasedRouterProperties.isEnabled()).thenReturn(true);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())).
thenReturn(testCallerService);
setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED);
Assert.assertEquals(1, routerMetadata.size());
Assert.assertEquals(0, request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA).size());
Assert.assertEquals(0, request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY).size());
Assert.assertEquals(1, request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED).size());
Assert.assertEquals("true", request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED)
.get(RuleBasedRouter.ROUTER_ENABLED));
}
}
@Test
public void testRouter() {
when(polarisRuleBasedRouterProperties.isEnabled()).thenReturn(true);
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(testCallerService);
setTransitiveMetadata();
PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier(
delegate, routerAPI, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties);
ProcessRoutersResponse assembleResponse = assembleProcessRoutersResponse();
when(routerAPI.processRouters(any())).thenReturn(assembleResponse);
Flux<List<ServiceInstance>> servers = compositeRule.doRouter(assembleServers(), assembleRouterContext());
Assert.assertEquals(assembleResponse.getServiceInstances().getInstances().size(),
servers.toStream().mapToLong(List::size).sum());
}
}
private void setTransitiveMetadata() {
if (initTransitiveMetadata.compareAndSet(false, true)) {
// mock transitive metadata
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
try (MockedStatic<MetadataContextHolder> mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class)) {
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
}
}
}
private ServiceInstances assembleServiceInstances() {
ServiceKey serviceKey = new ServiceKey(testNamespace, testCalleeService);
List<Instance> instances = new LinkedList<>();
instances.add(new DefaultInstance());
instances.add(new DefaultInstance());
instances.add(new DefaultInstance());
instances.add(new DefaultInstance());
instances.add(new DefaultInstance());
return new DefaultServiceInstances(serviceKey, instances);
}
private PolarisRouterContext assembleRouterContext() {
PolarisRouterContext routerContext = new PolarisRouterContext();
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
Map<String, String> routerLabels = new HashMap<>();
routerLabels.put("k2", "v2");
routerLabels.put("k3", "v3");
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, routerLabels);
return routerContext;
}
private ProcessRoutersResponse assembleProcessRoutersResponse() {
return new ProcessRoutersResponse(assembleServiceInstances());
}
private Flux<List<ServiceInstance>> assembleServers() {
ServiceInstances serviceInstances = assembleServiceInstances();
List<ServiceInstance> servers = new ArrayList<>();
for (Instance instance : serviceInstances.getInstances()) {
servers.add(new PolarisServiceInstance(instance));
}
return Flux.fromIterable(Collections.singletonList(servers));
}
}

@ -0,0 +1,93 @@
/*
* 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.router;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Lists;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RoutingProto;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
/**
* test for {@link RouterRuleLabelResolver}
*@author lepdou 2022-05-26
*/
@RunWith(MockitoJUnitRunner.class)
public class RouterRuleLabelResolverTest {
@Mock
private ServiceRuleManager serviceRuleManager;
private final String testNamespace = "testNamespace";
private final String testSourceService = "sourceService";
private final String testDstService = "dstService";
@Test
public void test() {
Map<String, ModelProto.MatchString> labels = new HashMap<>();
ModelProto.MatchString matchString = ModelProto.MatchString.getDefaultInstance();
String validKey1 = "${http.header.uid}";
String validKey2 = "${http.query.name}";
String validKey3 = "${http.method}";
String validKey4 = "${http.uri}";
String invalidKey = "${http.expression.wrong}";
labels.put(validKey1, matchString);
labels.put(validKey2, matchString);
labels.put(validKey3, matchString);
labels.put(validKey4, matchString);
labels.put(invalidKey, matchString);
RoutingProto.Source source1 = RoutingProto.Source.newBuilder().putAllMetadata(labels).build();
RoutingProto.Source source2 = RoutingProto.Source.newBuilder().putAllMetadata(labels).build();
RoutingProto.Source source3 = RoutingProto.Source.newBuilder().putAllMetadata(new HashMap<>()).build();
List<RoutingProto.Route> routes = new LinkedList<>();
RoutingProto.Route route = RoutingProto.Route.newBuilder()
.addAllSources(Lists.newArrayList(source1, source2, source3))
.build();
routes.add(route);
when(serviceRuleManager.getServiceRouterRule(testNamespace, testSourceService, testDstService)).thenReturn(routes);
RouterRuleLabelResolver resolver = new RouterRuleLabelResolver(serviceRuleManager);
Set<String> resolvedExpressionLabelKeys = resolver.getExpressionLabelKeys(testNamespace, testSourceService, testDstService);
Assert.assertNotNull(resolvedExpressionLabelKeys);
Assert.assertEquals(4, resolvedExpressionLabelKeys.size());
Assert.assertTrue(resolvedExpressionLabelKeys.contains(validKey1));
Assert.assertTrue(resolvedExpressionLabelKeys.contains(validKey2));
Assert.assertTrue(resolvedExpressionLabelKeys.contains(validKey3));
Assert.assertTrue(resolvedExpressionLabelKeys.contains(validKey4));
Assert.assertFalse(resolvedExpressionLabelKeys.contains(invalidKey));
}
}

@ -0,0 +1,140 @@
/*
* 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.router.feign;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.Sets;
import feign.Request;
import feign.RequestTemplate;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.util.StringUtils;
/**
* Test for {@link FeignExpressionLabelUtils}
*@author lepdou 2022-05-26
*/
public class FeignExpressionLabelUtilsTest {
@Test
public void testGetHeaderLabel() {
String headerKey = "uid";
String headerValue = "1000";
String headerKey2 = "teacher.age";
String headerValue2 = "1000";
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.header(headerKey, headerValue);
requestTemplate.header(headerKey2, headerValue2);
String labelKey1 = "${http.header.uid}";
String labelKey2 = "${http.header.name}";
String labelKey3 = "${http.headername}";
String labelKey4 = "${http.header.}";
String labelKey5 = "${http.header.teacher.age}";
Map<String, String> result = FeignExpressionLabelUtils.resolve(requestTemplate,
Sets.newHashSet(labelKey1, labelKey2, labelKey3, labelKey4, labelKey5));
Assert.assertFalse(result.isEmpty());
Assert.assertEquals(headerValue, result.get(labelKey1));
Assert.assertEquals(headerValue2, result.get(labelKey5));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey2)));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey3)));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey4)));
}
@Test
public void testGetQueryLabel() {
String headerKey = "uid";
String headerValue = "1000";
String headerKey2 = "teacher.age";
String headerValue2 = "1000";
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.query(headerKey, headerValue);
requestTemplate.query(headerKey2, headerValue2);
String labelKey1 = "${http.query.uid}";
String labelKey2 = "${http.query.name}";
String labelKey3 = "${http.queryname}";
String labelKey4 = "${http.query.}";
String labelKey5 = "${http.query.teacher.age}";
Map<String, String> result = FeignExpressionLabelUtils.resolve(requestTemplate,
Sets.newHashSet(labelKey1, labelKey2, labelKey3, labelKey4, labelKey5));
Assert.assertFalse(result.isEmpty());
Assert.assertEquals(headerValue, result.get(labelKey1));
Assert.assertEquals(headerValue2, result.get(labelKey5));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey2)));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey3)));
Assert.assertTrue(StringUtils.isEmpty(result.get(labelKey4)));
}
@Test
public void testGetMethod() {
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.method(Request.HttpMethod.GET);
String labelKey1 = "${http.method}";
Map<String, String> result = FeignExpressionLabelUtils.resolve(requestTemplate,
Sets.newHashSet(labelKey1));
Assert.assertFalse(result.isEmpty());
Assert.assertEquals("GET", result.get(labelKey1));
}
@Test
public void testGetUri() {
String uri = "/user/get";
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.uri(uri);
requestTemplate.method(Request.HttpMethod.GET);
requestTemplate.target("http://localhost");
requestTemplate = requestTemplate.resolve(new HashMap<>());
String labelKey1 = "${http.uri}";
Map<String, String> result = FeignExpressionLabelUtils.resolve(requestTemplate,
Sets.newHashSet(labelKey1));
Assert.assertFalse(result.isEmpty());
Assert.assertEquals(uri, result.get(labelKey1));
}
@Test
public void testGetUri2() {
String uri = "/";
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.uri(uri);
requestTemplate.method(Request.HttpMethod.GET);
requestTemplate.target("http://localhost");
requestTemplate = requestTemplate.resolve(new HashMap<>());
String labelKey1 = "${http.uri}";
Map<String, String> result = FeignExpressionLabelUtils.resolve(requestTemplate,
Sets.newHashSet(labelKey1));
Assert.assertFalse(result.isEmpty());
Assert.assertEquals(uri, result.get(labelKey1));
}
}

@ -0,0 +1,135 @@
/*
* 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.router.feign;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import feign.RequestTemplate;
import feign.Target;
import org.junit.Assert;
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 static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* test for {@link RouterLabelFeignInterceptor}
* @author lepdou 2022-05-26
*/
@RunWith(MockitoJUnitRunner.class)
public class RouterLabelFeignInterceptorTest {
@Mock
private MetadataLocalProperties metadataLocalProperties;
@Mock
private RouterRuleLabelResolver routerRuleLabelResolver;
@Mock
private RouterLabelResolver routerLabelResolver;
@Test
public void testResolveRouterLabel() throws UnsupportedEncodingException {
RouterLabelFeignInterceptor routerLabelFeignInterceptor = new RouterLabelFeignInterceptor(
Collections.singletonList(routerLabelResolver),
metadataLocalProperties, routerRuleLabelResolver);
// mock request template
RequestTemplate requestTemplate = new RequestTemplate();
String headerUidKey = "uid";
String headerUidValue = "1000";
requestTemplate.header(headerUidKey, headerUidValue);
String peerService = "peerService";
Target.EmptyTarget<Object> target = Target.EmptyTarget.create(Object.class, peerService);
requestTemplate.feignTarget(target);
// mock ApplicationContextAwareUtils#getProperties
try (MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class)) {
String testService = "callerService";
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(testService);
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
// mock transitive metadata
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
transitiveLabels.put("k2", "v22");
when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels);
// mock MetadataContextHolder#get
try (MockedStatic<MetadataContextHolder> mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class)) {
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
// mock custom resolved labels from request
Map<String, String> customResolvedLabels = new HashMap<>();
customResolvedLabels.put("k2", "v2");
customResolvedLabels.put("k3", "v3");
when(routerLabelResolver.resolve(requestTemplate)).thenReturn(customResolvedLabels);
// mock expression rule labels
Set<String> expressionKeys = new HashSet<>();
expressionKeys.add("${http.header.uid}");
expressionKeys.add("${http.header.name}");
when(routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, peerService)).thenReturn(expressionKeys);
// mock local metadata
Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k3", "v31");
localMetadata.put("k4", "v4");
when(metadataLocalProperties.getContent()).thenReturn(localMetadata);
routerLabelFeignInterceptor.apply(requestTemplate);
Collection<String> routerLabels = requestTemplate.headers().get(RouterConstants.ROUTER_LABEL_HEADER);
Assert.assertNotNull(routerLabels);
for (String value : routerLabels) {
Map<String, String> labels = JacksonUtils.deserialize2Map(URLDecoder.decode(value, "UTF-8"));
Assert.assertEquals("v1", labels.get("k1"));
Assert.assertEquals("v22", labels.get("k2"));
Assert.assertEquals("v3", labels.get("k3"));
Assert.assertEquals("v4", labels.get("k4"));
Assert.assertEquals(headerUidValue, labels.get("${http.header.uid}"));
Assert.assertEquals("", labels.get("${http.header.name}"));
}
}
}
}
}

@ -0,0 +1,93 @@
/*
* 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.router.resttemplate;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.BeanFactoryUtils;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.junit.Assert;
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.beans.factory.BeanFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import static org.mockito.Mockito.when;
/**
* Test for ${@link PolarisLoadBalancerBeanPostProcessor}
* @author lepdou 2022-05-26
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisLoadBalancerBeanPostProcessorTest {
@Mock
private LoadBalancerClient loadBalancerClient;
@Mock
private LoadBalancerRequestFactory loadBalancerRequestFactory;
@Mock
private MetadataLocalProperties metadataLocalProperties;
@Mock
private RouterRuleLabelResolver routerRuleLabelResolver;
@Mock
private BeanFactory beanFactory;
@Test
public void testWrapperLoadBalancerInterceptor() {
when(beanFactory.getBean(LoadBalancerRequestFactory.class)).thenReturn(loadBalancerRequestFactory);
when(beanFactory.getBean(LoadBalancerClient.class)).thenReturn(loadBalancerClient);
when(beanFactory.getBean(MetadataLocalProperties.class)).thenReturn(metadataLocalProperties);
when(beanFactory.getBean(RouterRuleLabelResolver.class)).thenReturn(routerRuleLabelResolver);
try (MockedStatic<BeanFactoryUtils> mockedBeanFactoryUtils = Mockito.mockStatic(BeanFactoryUtils.class)) {
mockedBeanFactoryUtils.when(() -> BeanFactoryUtils.getBeans(beanFactory, RouterLabelResolver.class))
.thenReturn(null);
LoadBalancerInterceptor loadBalancerInterceptor = new LoadBalancerInterceptor(loadBalancerClient, loadBalancerRequestFactory);
PolarisLoadBalancerBeanPostProcessor processor = new PolarisLoadBalancerBeanPostProcessor();
processor.setBeanFactory(beanFactory);
Object bean = processor.postProcessBeforeInitialization(loadBalancerInterceptor, "");
Assert.assertTrue(bean instanceof PolarisLoadBalancerInterceptor);
}
}
@Test
public void testNotWrapperLoadBalancerInterceptor() {
PolarisLoadBalancerBeanPostProcessor processor = new PolarisLoadBalancerBeanPostProcessor();
processor.setBeanFactory(beanFactory);
OtherBean otherBean = new OtherBean();
Object bean = processor.postProcessBeforeInitialization(otherBean, "");
Assert.assertFalse(bean instanceof PolarisLoadBalancerInterceptor);
Assert.assertTrue(bean instanceof OtherBean);
}
static class OtherBean {
}
}

@ -0,0 +1,231 @@
/*
* 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.router.resttemplate;
import java.net.URI;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.junit.AfterClass;
import org.junit.Assert;
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 org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* test for {@link PolarisLoadBalancerInterceptor}
* @author lepdou 2022-05-26
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisLoadBalancerInterceptorTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<MetadataContextHolder> mockedMetadataContextHolder;
@Mock
private LoadBalancerClient loadBalancerClient;
@Mock
private LoadBalancerRequestFactory loadBalancerRequestFactory;
@Mock
private RouterLabelResolver routerLabelResolver;
@Mock
private MetadataLocalProperties metadataLocalProperties;
@Mock
private RouterRuleLabelResolver routerRuleLabelResolver;
@BeforeClass
public static void beforeClass() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("callerService");
mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class);
}
@AfterClass
public static void afterClass() {
mockedApplicationContextAwareUtils.close();
mockedMetadataContextHolder.close();
}
@Test
public void testProxyRibbonLoadBalance() throws Exception {
String callerService = "callerService";
String calleeService = "calleeService";
HttpRequest request = new MockedHttpRequest("http://" + calleeService + "/user/get");
// mock local metadata
Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k1", "v1");
localMetadata.put("k2", "v2");
when(metadataLocalProperties.getContent()).thenReturn(localMetadata);
// mock custom resolved from request
Map<String, String> customResolvedLabels = new HashMap<>();
customResolvedLabels.put("k3", "v3");
customResolvedLabels.put("k4", "v4");
when(routerLabelResolver.resolve(request, null)).thenReturn(customResolvedLabels);
// mock expression rule labels
Set<String> expressionKeys = new HashSet<>();
expressionKeys.add("${http.method}");
expressionKeys.add("${http.uri}");
when(routerRuleLabelResolver.getExpressionLabelKeys(callerService, callerService, calleeService)).thenReturn(expressionKeys);
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
// mock transitive metadata
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
transitiveLabels.put("k2", "v22");
when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels);
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
LoadBalancerRequest<ClientHttpResponse> loadBalancerRequest = new MockedLoadBalancerRequest<>();
when(loadBalancerRequestFactory.createRequest(request, null, null)).thenReturn(loadBalancerRequest);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient,
loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), metadataLocalProperties, routerRuleLabelResolver);
polarisLoadBalancerInterceptor.intercept(request, null, null);
verify(metadataLocalProperties).getContent();
verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService);
verify(routerLabelResolver).resolve(request, null);
}
@Test
public void testRouterContext() throws Exception {
String callerService = "callerService";
String calleeService = "calleeService";
HttpRequest request = new MockedHttpRequest("http://" + calleeService + "/user/get");
// mock local metadata
Map<String, String> localMetadata = new HashMap<>();
localMetadata.put("k1", "v1");
localMetadata.put("k2", "v2");
when(metadataLocalProperties.getContent()).thenReturn(localMetadata);
// mock custom resolved from request
Map<String, String> customResolvedLabels = new HashMap<>();
customResolvedLabels.put("k2", "v22");
customResolvedLabels.put("k4", "v4");
when(routerLabelResolver.resolve(request, null)).thenReturn(customResolvedLabels);
// mock expression rule labels
Set<String> expressionKeys = new HashSet<>();
expressionKeys.add("${http.method}");
expressionKeys.add("${http.uri}");
when(routerRuleLabelResolver.getExpressionLabelKeys(callerService, callerService, calleeService)).thenReturn(expressionKeys);
MetadataContext metadataContext = Mockito.mock(MetadataContext.class);
// mock transitive metadata
Map<String, String> transitiveLabels = new HashMap<>();
transitiveLabels.put("k1", "v1");
transitiveLabels.put("k2", "v22");
when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels);
mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext);
PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient,
loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), metadataLocalProperties, routerRuleLabelResolver);
polarisLoadBalancerInterceptor.setLabelsToHeaders(request, null, calleeService);
verify(metadataLocalProperties).getContent();
verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService);
verify(routerLabelResolver).resolve(request, null);
Map<String, String> headers = JacksonUtils.deserialize2Map(URLDecoder.decode(request.getHeaders()
.get(RouterConstants.ROUTER_LABEL_HEADER).get(0), "UTF-8"));
Assert.assertEquals("v1", headers.get("k1"));
Assert.assertEquals("v22", headers.get("k2"));
Assert.assertEquals("v4", headers.get("k4"));
Assert.assertEquals("GET", headers.get("${http.method}"));
Assert.assertEquals("/user/get", headers.get("${http.uri}"));
}
static class MockedLoadBalancerRequest<T> implements LoadBalancerRequest<T> {
@Override
public T apply(ServiceInstance instance) throws Exception {
return null;
}
}
static class MockedHttpRequest implements HttpRequest {
private URI uri;
private HttpHeaders httpHeaders = new HttpHeaders();
MockedHttpRequest(String url) {
this.uri = URI.create(url);
}
@Override
public String getMethodValue() {
return HttpMethod.GET.name();
}
@Override
public URI getURI() {
return uri;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
}
}

@ -66,20 +66,26 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-cloud-starter-gateway</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId> <artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>

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

Loading…
Cancel
Save