From da9b66f8d83548a4b77faee92153634d7ddbb478 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Thu, 14 Aug 2025 16:28:21 +0800 Subject: [PATCH] feat: support tsf gw. (#1697) Co-authored-by: shedfreewu <49236872+shedfreewu@users.noreply.github.com> --- CHANGELOG.md | 1 + .../MetadataTransferAutoConfiguration.java | 68 ++- .../DecodeTransferMetadataReactiveFilter.java | 36 +- .../DecodeTransferMetadataServletFilter.java | 30 +- ...codeTransferMedataFeignEnhancedPlugin.java | 25 +- ...ransferMedataRestTemplateInterceptor.java} | 66 +-- ...EncodeTransferMedataScgEnhancedPlugin.java | 17 +- ...TransferMedataWebClientEnhancedPlugin.java | 19 +- .../core/HttpServletRequestHeaderWrapper.java | 44 ++ ...MetadataTransferAutoConfigurationTest.java | 3 - ...sferMedataRestTemplateInterceptorTest.java | 2 +- .../PolarisCircuitBreakerTest.java | 9 +- .../common/PolarisResultToErrorCodeTest.java | 15 +- ...gnCircuitBreakerInvocationHandlerTest.java | 28 +- .../feign/PolarisFeignCircuitBreakerTest.java | 18 +- ...PolarisCircuitBreakerHttpResponseTest.java | 63 +-- .../adapter/PolarisConfigFileLocatorTest.java | 30 +- .../RouterBootstrapAutoConfigurationTest.java | 3 +- .../common/constant/MetadataConstant.java | 18 +- .../cloud/common/constant/OrderConstant.java | 2 +- .../cloud/common/tsf/TsfContextUtils.java | 8 + .../cloud/common/util/JacksonUtils.java | 28 ++ .../cloud/common/util/TsfTagUtils.java | 231 +++++++++ .../tsf/core/entity/Metadata.java | 152 ++++++ .../springframework/tsf/core/entity/Tag.java | 14 +- .../cloud/common/util/TsfTagUtilsTest.java | 43 ++ spring-cloud-tencent-dependencies/pom.xml | 2 +- .../tsf-example/msgw-scg/pom.xml | 32 ++ .../cloud/tsf/msgw/scg/ScgApplication.java | 34 ++ .../msgw-scg/src/main/resources/bootstrap.yml | 28 ++ .../tsf-example/pom.xml | 1 + .../GatewayPluginAutoConfiguration.java | 80 ++- ...larisReactiveLoadBalancerClientFilter.java | 2 +- ...BalancerClientFilterBeanPostProcessor.java | 2 +- .../plugin/gateway/context/AuthCheckUtil.java | 119 +++++ .../gateway/context/ContextGatewayFilter.java | 352 +++++++++++-- .../context/ContextGatewayProperties.java | 23 + .../ContextGatewayPropertiesManager.java | 186 ++++++- .../context/ContextRoutePredicateFactory.java | 17 +- .../context/GatewayConfigChangeListener.java | 13 +- .../gateway/context/GatewayConsulRepo.java | 474 ++++++++++++++++++ .../plugin/gateway/context/GroupContext.java | 125 +++++ .../plugin/gateway/context/PathRewrite.java | 133 +++++ .../gateway/context/PathRewriteUtil.java | 65 +++ .../plugin/gateway/context/Position.java | 22 + .../tsf/gateway/core/TsfGatewayRequest.java | 128 +++++ .../core/annotation/TsfGatewayFilter.java | 38 ++ .../tsf/gateway/core/constant/AuthMode.java | 49 ++ .../gateway/core/constant/CommonStatus.java | 61 +++ .../core/constant/GatewayConstant.java | 82 +++ .../tsf/gateway/core/constant/HeaderName.java | 57 +++ .../tsf/gateway/core/constant/HttpMethod.java | 56 +++ .../core/constant/PluginConstants.java | 68 +++ .../core/constant/PluginScopeType.java | 54 ++ .../tsf/gateway/core/constant/PluginType.java | 99 ++++ .../tsf/gateway/core/constant/TsfAlgType.java | 69 +++ .../core/exception/TsfGatewayError.java | 142 ++++++ .../core/exception/TsfGatewayException.java | 44 ++ .../gateway/core/http/HttpConfigConstant.java | 52 ++ .../core/http/HttpConnectionPoolUtil.java | 345 +++++++++++++ .../tsf/gateway/core/model/ClaimMapping.java | 63 +++ .../gateway/core/model/GatewayAllResult.java | 70 +++ .../tsf/gateway/core/model/GatewayResult.java | 89 ++++ .../tencent/tsf/gateway/core/model/Group.java | 171 +++++++ .../tsf/gateway/core/model/GroupApi.java | 221 ++++++++ .../gateway/core/model/GroupApiResult.java | 26 + .../tsf/gateway/core/model/GroupResult.java | 26 + .../tsf/gateway/core/model/GroupSecret.java | 104 ++++ .../tsf/gateway/core/model/JwtPlugin.java | 119 +++++ .../tsf/gateway/core/model/OAuthPlugin.java | 166 ++++++ .../tsf/gateway/core/model/OAuthResult.java | 51 ++ .../gateway/core/model/PathRewriteResult.java | 28 ++ .../core/model/PathWildcardResult.java | 25 + .../gateway/core/model/PathWildcardRule.java | 176 +++++++ .../tsf/gateway/core/model/PluginArgInfo.java | 68 +++ .../tsf/gateway/core/model/PluginDetail.java | 60 +++ .../tsf/gateway/core/model/PluginInfo.java | 128 +++++ .../core/model/PluginInstanceInfo.java | 71 +++ .../core/model/PluginInstanceInfoResult.java | 26 + .../tsf/gateway/core/model/PluginPayload.java | 103 ++++ .../core/model/RequestTransformerPlugin.java | 85 ++++ .../model/RequestTransformerPluginInfo.java | 58 +++ .../tsf/gateway/core/model/TagPlugin.java | 61 +++ .../tsf/gateway/core/model/TagPluginInfo.java | 81 +++ .../gateway/core/model/TransformerAction.java | 92 ++++ .../gateway/core/model/TransformerTag.java | 41 ++ .../core/plugin/GatewayPluginFactory.java | 59 +++ .../gateway/core/plugin/IGatewayPlugin.java | 35 ++ .../gateway/core/plugin/JwtGatewayPlugin.java | 174 +++++++ .../core/plugin/OAuthGatewayPlugin.java | 304 +++++++++++ .../plugin/ReqTransformerGatewayPlugin.java | 40 ++ .../gateway/core/plugin/TagGatewayPlugin.java | 46 ++ .../tsf/gateway/core/util/CookieUtil.java | 47 ++ .../tsf/gateway/core/util/IdGenerator.java | 95 ++++ .../tsf/gateway/core/util/PluginUtil.java | 339 +++++++++++++ .../tsf/gateway/core/util/TsfSignUtil.java | 73 +++ .../gateway/scg/AbstractTsfGlobalFilter.java | 50 ++ .../GatewayPluginAutoConfigurationTest.java | 6 + ...ncerClientFilterBeanPostProcessorTest.java | 8 +- ...sReactiveLoadBalancerClientFilterTest.java | 10 +- .../ContextGatewayPropertiesManagerTest.java | 25 +- .../ContextRoutePredicateFactoryTest.java | 14 +- .../PolarisSpanAttributesProvider.java | 5 +- .../tsf/TsfCoreEnvironmentPostProcessor.java | 11 + ...adBalancerClientBeanPostProcessorTest.java | 6 +- .../scg/EnhancedGatewayGlobalFilterTest.java | 6 +- ...edWebClientExchangeFilterFunctionTest.java | 3 +- .../DefaultEnhancedPluginRunnerTest.java | 5 +- 108 files changed, 7346 insertions(+), 251 deletions(-) rename spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/{EncodeTransferMedataRestTemplateEnhancedPlugin.java => EncodeTransferMedataRestTemplateInterceptor.java} (62%) create mode 100644 spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/HttpServletRequestHeaderWrapper.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/TsfTagUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/org/springframework/tsf/core/entity/Metadata.java create mode 100644 spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/TsfTagUtilsTest.java create mode 100644 spring-cloud-tencent-examples/tsf-example/msgw-scg/pom.xml create mode 100644 spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/java/com/tencent/cloud/tsf/msgw/scg/ScgApplication.java create mode 100644 spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/AuthCheckUtil.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GatewayConsulRepo.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/PathRewrite.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/PathRewriteUtil.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/TsfGatewayRequest.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/annotation/TsfGatewayFilter.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/AuthMode.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/CommonStatus.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/GatewayConstant.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/HeaderName.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/HttpMethod.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginConstants.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginScopeType.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginType.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/TsfAlgType.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/exception/TsfGatewayError.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/exception/TsfGatewayException.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/http/HttpConfigConstant.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/http/HttpConnectionPoolUtil.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/ClaimMapping.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GatewayAllResult.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GatewayResult.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/Group.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupApi.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupApiResult.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupResult.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupSecret.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/JwtPlugin.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/OAuthPlugin.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/OAuthResult.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathRewriteResult.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathWildcardResult.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathWildcardRule.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginArgInfo.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginDetail.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInfo.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInstanceInfo.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInstanceInfoResult.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginPayload.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/RequestTransformerPlugin.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/RequestTransformerPluginInfo.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TagPlugin.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TagPluginInfo.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TransformerAction.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TransformerTag.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/GatewayPluginFactory.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/IGatewayPlugin.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/JwtGatewayPlugin.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/OAuthGatewayPlugin.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/ReqTransformerGatewayPlugin.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/TagGatewayPlugin.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/CookieUtil.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/IdGenerator.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/PluginUtil.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/TsfSignUtil.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/scg/AbstractTsfGlobalFilter.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f35c9c71a..8e5564850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,3 +5,4 @@ - [fix: fix ConfigChangeListener and unit test](https://github.com/Tencent/spring-cloud-tencent/pull/1654) - [feat: support spring-retry and feign config refresh and feign eager load support schema](https://github.com/Tencent/spring-cloud-tencent/pull/1649) - [fix: fix ConfigChangeListener ut bug](https://github.com/Tencent/spring-cloud-tencent/pull/1659) +- [feat: support tsf gw.](https://github.com/Tencent/spring-cloud-tencent/pull/1696) diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java index 8df293f1c..ddb077213 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java @@ -17,20 +17,32 @@ package com.tencent.cloud.metadata.config; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.metadata.core.DecodeTransferMetadataReactiveFilter; import com.tencent.cloud.metadata.core.DecodeTransferMetadataServletFilter; import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignEnhancedPlugin; -import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateEnhancedPlugin; +import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor; import com.tencent.cloud.metadata.core.EncodeTransferMedataScgEnhancedPlugin; import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientEnhancedPlugin; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; +import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer; +import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.util.CollectionUtils; +import org.springframework.web.client.RestTemplate; import static javax.servlet.DispatcherType.ASYNC; import static javax.servlet.DispatcherType.ERROR; @@ -118,9 +130,59 @@ public class MetadataTransferAutoConfiguration { @ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true) protected static class MetadataTransferRestTemplateConfig { + @Autowired(required = false) + private List restTemplates = Collections.emptyList(); + + @Bean + public EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor() { + return new EncodeTransferMedataRestTemplateInterceptor(); + } + + @Bean + public SmartInitializingSingleton addEncodeTransferMetadataInterceptorForRestTemplate(EncodeTransferMedataRestTemplateInterceptor interceptor) { + return () -> restTemplates.forEach(restTemplate -> { + List list = new ArrayList<>(restTemplate.getInterceptors()); + list.add(interceptor); + restTemplate.setInterceptors(list); + }); + } + @Bean - public EncodeTransferMedataRestTemplateEnhancedPlugin encodeTransferMedataRestTemplateEnhancedPlugin() { - return new EncodeTransferMedataRestTemplateEnhancedPlugin(); + public RestTemplateCustomizer polarisRestTemplateCustomizer( + @Autowired(required = false) RetryLoadBalancerInterceptor retryLoadBalancerInterceptor, + @Autowired(required = false) LoadBalancerInterceptor loadBalancerInterceptor) { + return restTemplate -> { + List list = new ArrayList<>(restTemplate.getInterceptors()); + // LoadBalancerInterceptor must invoke before EncodeTransferMedataRestTemplateInterceptor + int addIndex = list.size(); + if (CollectionUtils.containsInstance(list, retryLoadBalancerInterceptor) || CollectionUtils.containsInstance(list, loadBalancerInterceptor)) { + ClientHttpRequestInterceptor enhancedRestTemplateInterceptor = null; + for (int i = 0; i < list.size(); i++) { + if (list.get(i) instanceof EncodeTransferMedataRestTemplateInterceptor) { + enhancedRestTemplateInterceptor = list.get(i); + addIndex = i; + } + } + if (enhancedRestTemplateInterceptor != null) { + list.remove(addIndex); + list.add(enhancedRestTemplateInterceptor); + } + } + else { + if (retryLoadBalancerInterceptor != null || loadBalancerInterceptor != null) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i) instanceof EncodeTransferMedataRestTemplateInterceptor) { + addIndex = i; + } + } + list.add(addIndex, + retryLoadBalancerInterceptor != null + ? retryLoadBalancerInterceptor + : loadBalancerInterceptor); + } + } + restTemplate.setInterceptors(list); + }; } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java index c1330458a..46924062d 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java @@ -19,12 +19,14 @@ package com.tencent.cloud.metadata.core; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.common.util.TsfTagUtils; import com.tencent.cloud.common.util.UrlUtils; import com.tencent.cloud.metadata.provider.ReactiveMetadataProvider; import com.tencent.polaris.api.utils.StringUtils; @@ -64,30 +66,47 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered // Get metadata string from http header. ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest(); + Map mergedTransitiveMetadata = new HashMap<>(); + Map mergedDisposableMetadata = new HashMap<>(); + Map mergedApplicationMetadata = new HashMap<>(); + // some tsf headers need to change to polaris header + Map addHeaders = new HashMap<>(); + AtomicReference callerIp = new AtomicReference<>(""); + + TsfTagUtils.updateTsfMetadata(mergedTransitiveMetadata, mergedDisposableMetadata, + mergedApplicationMetadata, addHeaders, callerIp, + serverHttpRequest.getHeaders().getFirst(MetadataConstant.HeaderName.TSF_TAGS), + serverHttpRequest.getHeaders().getFirst(MetadataConstant.HeaderName.TSF_SYSTEM_TAG), + serverHttpRequest.getHeaders().getFirst(MetadataConstant.HeaderName.TSF_METADATA)); + // transitive metadata // from specific header Map internalTransitiveMetadata = getInternalMetadata(serverHttpRequest, CUSTOM_METADATA); // from header with specific prefix Map customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(serverWebExchange); - Map mergedTransitiveMetadata = new HashMap<>(); mergedTransitiveMetadata.putAll(internalTransitiveMetadata); mergedTransitiveMetadata.putAll(customTransitiveMetadata); // disposable metadata // from specific header Map internalDisposableMetadata = getInternalMetadata(serverHttpRequest, CUSTOM_DISPOSABLE_METADATA); - Map mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata); + mergedDisposableMetadata.putAll(internalDisposableMetadata); // application metadata Map internalApplicationMetadata = getInternalMetadata(serverHttpRequest, APPLICATION_METADATA); - Map mergedApplicationMetadata = new HashMap<>(internalApplicationMetadata); + mergedApplicationMetadata.putAll(internalApplicationMetadata); - String callerIp = ""; if (StringUtils.isNotBlank(mergedApplicationMetadata.get(LOCAL_IP))) { - callerIp = mergedApplicationMetadata.get(LOCAL_IP); + callerIp.set(mergedApplicationMetadata.get(LOCAL_IP)); } + // add headers + serverHttpRequest = serverHttpRequest.mutate().headers(httpHeaders -> { + for (Map.Entry entry : addHeaders.entrySet()) { + httpHeaders.add(entry.getKey(), entry.getValue()); + } + }).build(); // message metadata - ReactiveMetadataProvider callerMessageMetadataProvider = new ReactiveMetadataProvider(serverHttpRequest, callerIp); + ReactiveMetadataProvider callerMessageMetadataProvider = new ReactiveMetadataProvider(serverHttpRequest, callerIp.get()); MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, mergedApplicationMetadata, callerMessageMetadataProvider); @@ -97,6 +116,11 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered MetadataContextHolder.get()); String targetNamespace = serverWebExchange.getRequest().getHeaders().getFirst(MetadataConstant.HeaderName.NAMESPACE); + // Compatible with TSF + if (StringUtils.isBlank(targetNamespace)) { + targetNamespace = serverWebExchange.getRequest().getHeaders().getFirst(MetadataConstant.HeaderName.TSF_NAMESPACE_ID); + } + if (StringUtils.isNotBlank(targetNamespace)) { MetadataContextHolder.get().putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE, MetadataConstant.POLARIS_TARGET_NAMESPACE, targetNamespace); diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java index 7edc7e078..582f1ece8 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java @@ -20,15 +20,18 @@ package com.tencent.cloud.metadata.core; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.common.util.TsfTagUtils; import com.tencent.cloud.common.util.UrlUtils; import com.tencent.cloud.metadata.provider.ServletMetadataProvider; import com.tencent.polaris.api.utils.StringUtils; @@ -59,30 +62,45 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter { protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest, @NonNull HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { + Map mergedTransitiveMetadata = new HashMap<>(); + Map mergedDisposableMetadata = new HashMap<>(); + Map mergedApplicationMetadata = new HashMap<>(); + // some tsf headers need to change to polaris header + Map addHeaders = new HashMap<>(); + AtomicReference callerIp = new AtomicReference<>(""); + + TsfTagUtils.updateTsfMetadata(mergedTransitiveMetadata, mergedDisposableMetadata, + mergedApplicationMetadata, addHeaders, callerIp, + httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_TAGS), + httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_SYSTEM_TAG), + httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_METADATA)); + // transitive metadata // from specific header Map internalTransitiveMetadata = getInternalMetadata(httpServletRequest, CUSTOM_METADATA); // from header with specific prefix Map customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(httpServletRequest); - Map mergedTransitiveMetadata = new HashMap<>(); + mergedTransitiveMetadata.putAll(internalTransitiveMetadata); mergedTransitiveMetadata.putAll(customTransitiveMetadata); // disposable metadata // from specific header Map internalDisposableMetadata = getInternalMetadata(httpServletRequest, CUSTOM_DISPOSABLE_METADATA); - Map mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata); + mergedDisposableMetadata.putAll(internalDisposableMetadata); // application metadata Map internalApplicationMetadata = getInternalMetadata(httpServletRequest, APPLICATION_METADATA); - Map mergedApplicationMetadata = new HashMap<>(internalApplicationMetadata); + mergedApplicationMetadata.putAll(internalApplicationMetadata); + - String callerIp = ""; if (StringUtils.isNotBlank(mergedApplicationMetadata.get(LOCAL_IP))) { - callerIp = mergedApplicationMetadata.get(LOCAL_IP); + callerIp.set(mergedApplicationMetadata.get(LOCAL_IP)); } + // add headers + httpServletRequest = new HttpServletRequestHeaderWrapper(httpServletRequest, addHeaders); // message metadata - ServletMetadataProvider callerMessageMetadataProvider = new ServletMetadataProvider(httpServletRequest, callerIp); + ServletMetadataProvider callerMessageMetadataProvider = new ServletMetadataProvider(httpServletRequest, callerIp.get()); MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, mergedApplicationMetadata, callerMessageMetadataProvider); diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignEnhancedPlugin.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignEnhancedPlugin.java index 0949c2677..8b7ba78e0 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignEnhancedPlugin.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignEnhancedPlugin.java @@ -24,8 +24,10 @@ import java.util.Map; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.tsf.TsfContextUtils; import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.ReflectionUtils; +import com.tencent.cloud.common.util.TsfTagUtils; import com.tencent.cloud.common.util.UrlUtils; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; @@ -69,18 +71,23 @@ public class EncodeTransferMedataFeignEnhancedPlugin implements EnhancedPlugin { MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false); Map calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders(); - // currently only support transitive header from calleeMessageMetadataContainer - this.buildHeaderMap(request, calleeTransitiveHeaders); - - // build custom disposable metadata request header - this.buildMetadataHeader(request, disposableMetadata, CUSTOM_DISPOSABLE_METADATA); + if (TsfContextUtils.isOnlyTsfConsulEnabled()) { + Map tsfMetadataMap = TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata); + this.buildHeaderMap(request, tsfMetadataMap); + } + else { + // currently only support transitive header from calleeMessageMetadataContainer + this.buildHeaderMap(request, calleeTransitiveHeaders); - // process custom metadata - this.buildMetadataHeader(request, customMetadata, CUSTOM_METADATA); + // build custom disposable metadata request header + this.buildMetadataHeader(request, disposableMetadata, CUSTOM_DISPOSABLE_METADATA); - // add application metadata - this.buildMetadataHeader(request, applicationMetadata, APPLICATION_METADATA); + // process custom metadata + this.buildMetadataHeader(request, customMetadata, CUSTOM_METADATA); + // add application metadata + this.buildMetadataHeader(request, applicationMetadata, APPLICATION_METADATA); + } // set headers that need to be transmitted from the upstream this.buildTransmittedHeader(request, transHeaders); } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateEnhancedPlugin.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptor.java similarity index 62% rename from spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateEnhancedPlugin.java rename to spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptor.java index 9aa0dc507..2c8e5af23 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateEnhancedPlugin.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptor.java @@ -17,21 +17,27 @@ package com.tencent.cloud.metadata.core; +import java.io.IOException; import java.util.Map; +import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.tsf.TsfContextUtils; import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.common.util.TsfTagUtils; import com.tencent.cloud.common.util.UrlUtils; -import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; -import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; -import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; -import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant; import com.tencent.polaris.metadata.core.MessageMetadataContainer; import com.tencent.polaris.metadata.core.MetadataType; import shade.polaris.com.google.common.collect.ImmutableMap; +import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; +import org.springframework.core.Ordered; import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.lang.NonNull; import org.springframework.util.CollectionUtils; import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.APPLICATION_METADATA; @@ -39,45 +45,54 @@ import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUST import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA; /** - * Pre EnhancedPlugin for rest template to encode transfer metadata. + * Interceptor used for adding the metadata in http headers from context when web client + * is RestTemplate. * - * @author Shedfree Wu + * It needs to execute after {@link LoadBalancerInterceptor}, because LaneRouter may add calleeTransitiveHeaders. + * + * @author Haotian Zhang */ -public class EncodeTransferMedataRestTemplateEnhancedPlugin implements EnhancedPlugin { +public class EncodeTransferMedataRestTemplateInterceptor implements ClientHttpRequestInterceptor, Ordered { + @Override - public EnhancedPluginType getType() { - return EnhancedPluginType.Client.PRE; + public int getOrder() { + return OrderConstant.Client.RestTemplate.ENCODE_TRANSFER_METADATA_INTERCEPTOR_ORDER; } @Override - public void run(EnhancedPluginContext context) throws Throwable { - if (!(context.getOriginRequest() instanceof HttpRequest)) { - return; - } - HttpRequest httpRequest = (HttpRequest) context.getOriginRequest(); - + public ClientHttpResponse intercept(@NonNull HttpRequest httpRequest, @NonNull byte[] bytes, + @NonNull ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { // get metadata of current thread MetadataContext metadataContext = MetadataContextHolder.get(); Map customMetadata = metadataContext.getCustomMetadata(); Map disposableMetadata = metadataContext.getDisposableMetadata(); Map applicationMetadata = metadataContext.getApplicationMetadata(); Map transHeaders = metadataContext.getTransHeadersKV(); + MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false); Map calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders(); - // currently only support transitive header from calleeMessageMetadataContainer - this.buildHeaderMap(httpRequest, calleeTransitiveHeaders); - // build custom disposable metadata request header - this.buildMetadataHeader(httpRequest, disposableMetadata, CUSTOM_DISPOSABLE_METADATA); + if (TsfContextUtils.isOnlyTsfConsulEnabled()) { + Map tsfMetadataMap = TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata); + this.buildHeaderMap(httpRequest, tsfMetadataMap); + } + else { + // currently only support transitive header from calleeMessageMetadataContainer + this.buildHeaderMap(httpRequest, calleeTransitiveHeaders); - // build custom metadata request header - this.buildMetadataHeader(httpRequest, customMetadata, CUSTOM_METADATA); + // build custom disposable metadata request header + this.buildMetadataHeader(httpRequest, disposableMetadata, CUSTOM_DISPOSABLE_METADATA); - // build application metadata request header - this.buildMetadataHeader(httpRequest, applicationMetadata, APPLICATION_METADATA); + // build custom metadata request header + this.buildMetadataHeader(httpRequest, customMetadata, CUSTOM_METADATA); + // build application metadata request header + this.buildMetadataHeader(httpRequest, applicationMetadata, APPLICATION_METADATA); + } // set headers that need to be transmitted from the upstream this.buildTransmittedHeader(httpRequest, transHeaders); + + return clientHttpRequestExecution.execute(httpRequest, bytes); } private void buildTransmittedHeader(HttpRequest request, Map transHeaders) { @@ -106,9 +121,4 @@ public class EncodeTransferMedataRestTemplateEnhancedPlugin implements EnhancedP buildHeaderMap(request, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata))); } } - - @Override - public int getOrder() { - return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER; - } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgEnhancedPlugin.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgEnhancedPlugin.java index 3dc6f1623..a1b560d75 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgEnhancedPlugin.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgEnhancedPlugin.java @@ -22,7 +22,9 @@ import java.util.Map; import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.tsf.TsfContextUtils; import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.common.util.TsfTagUtils; import com.tencent.cloud.common.util.UrlUtils; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; @@ -73,12 +75,17 @@ public class EncodeTransferMedataScgEnhancedPlugin implements EnhancedPlugin { MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false); Map calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders(); - // currently only support transitive header from calleeMessageMetadataContainer - this.buildHeaderMap(builder, calleeTransitiveHeaders); + if (TsfContextUtils.isOnlyTsfConsulEnabled()) { + this.buildHeaderMap(builder, TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata)); + } + else { + // currently only support transitive header from calleeMessageMetadataContainer + this.buildHeaderMap(builder, calleeTransitiveHeaders); - this.buildMetadataHeader(builder, customMetadata, CUSTOM_METADATA); - this.buildMetadataHeader(builder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA); - this.buildMetadataHeader(builder, applicationMetadata, APPLICATION_METADATA); + this.buildMetadataHeader(builder, customMetadata, CUSTOM_METADATA); + this.buildMetadataHeader(builder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA); + this.buildMetadataHeader(builder, applicationMetadata, APPLICATION_METADATA); + } TransHeadersTransfer.transfer(exchange.getRequest()); context.setOriginRequest(exchange.mutate().request(builder.build()).build()); diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientEnhancedPlugin.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientEnhancedPlugin.java index d764b012e..7e7aaa2d8 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientEnhancedPlugin.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientEnhancedPlugin.java @@ -21,7 +21,9 @@ import java.util.Map; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.tsf.TsfContextUtils; import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.common.util.TsfTagUtils; import com.tencent.cloud.common.util.UrlUtils; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; @@ -65,15 +67,18 @@ public class EncodeTransferMedataWebClientEnhancedPlugin implements EnhancedPlug Map calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders(); ClientRequest.Builder requestBuilder = ClientRequest.from(clientRequest); + if (TsfContextUtils.isOnlyTsfConsulEnabled()) { + this.buildHeaderMap(requestBuilder, TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata)); + } + else { + // currently only support transitive header from calleeMessageMetadataContainer + this.buildHeaderMap(requestBuilder, calleeTransitiveHeaders); - // currently only support transitive header from calleeMessageMetadataContainer - this.buildHeaderMap(requestBuilder, calleeTransitiveHeaders); - - this.buildMetadataHeader(requestBuilder, customMetadata, CUSTOM_METADATA); - this.buildMetadataHeader(requestBuilder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA); - this.buildMetadataHeader(requestBuilder, applicationMetadata, APPLICATION_METADATA); + this.buildMetadataHeader(requestBuilder, customMetadata, CUSTOM_METADATA); + this.buildMetadataHeader(requestBuilder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA); + this.buildMetadataHeader(requestBuilder, applicationMetadata, APPLICATION_METADATA); + } this.buildTransmittedHeader(requestBuilder, transHeaders); - context.setOriginRequest(requestBuilder.build()); } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/HttpServletRequestHeaderWrapper.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/HttpServletRequestHeaderWrapper.java new file mode 100644 index 000000000..9b5e16cbd --- /dev/null +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/HttpServletRequestHeaderWrapper.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + + +public class HttpServletRequestHeaderWrapper extends HttpServletRequestWrapper { + + private Map addHeaders; + + public HttpServletRequestHeaderWrapper(HttpServletRequest request, Map addHeaders) { + super(request); + this.addHeaders = addHeaders; + } + + + @Override + public String getHeader(String name) { + if (addHeaders.containsKey(name)) { + return addHeaders.get(name); + } + else { + return super.getHeader(name); + } + } +} diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java index a106265fc..7813e5add 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java @@ -18,7 +18,6 @@ package com.tencent.cloud.metadata.config; import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignEnhancedPlugin; -import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateEnhancedPlugin; import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientEnhancedPlugin; import com.tencent.cloud.polaris.context.config.PolarisContextProperties; import org.junit.jupiter.api.Test; @@ -52,7 +51,6 @@ public class MetadataTransferAutoConfigurationTest { .run(context -> { assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferFeignInterceptorConfig.class); assertThat(context).hasSingleBean(EncodeTransferMedataFeignEnhancedPlugin.class); - assertThat(context).hasSingleBean(EncodeTransferMedataRestTemplateEnhancedPlugin.class); assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class); assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferScgFilterConfig.class); }); @@ -68,7 +66,6 @@ public class MetadataTransferAutoConfigurationTest { assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferFeignInterceptorConfig.class); assertThat(context).hasSingleBean(EncodeTransferMedataFeignEnhancedPlugin.class); assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class); - assertThat(context).hasSingleBean(EncodeTransferMedataRestTemplateEnhancedPlugin.class); assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferScgFilterConfig.class); assertThat(context).hasSingleBean(EncodeTransferMedataWebClientEnhancedPlugin.class); }); diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptorTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptorTest.java index ce8d70fe9..ef4a7ac4e 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptorTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptorTest.java @@ -43,7 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** - * Test for {@link EncodeTransferMedataRestTemplateEnhancedPlugin}. + * Test for {@link EncodeTransferMedataRestTemplateInterceptor}. * * @author Haotian Zhang */ diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java index d5c06d590..a7cf3fa97 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java @@ -30,7 +30,6 @@ import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; import com.tencent.polaris.client.util.Utils; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -101,16 +100,16 @@ public class PolarisCircuitBreakerTest { Method getConfigurationsMethod = ReflectionUtils.findMethod(PolarisCircuitBreakerFactory.class, "getConfigurations"); - Assertions.assertNotNull(getConfigurationsMethod); + assertThat(getConfigurationsMethod).isNotNull(); ReflectionUtils.makeAccessible(getConfigurationsMethod); Map values = (Map) ReflectionUtils.invokeMethod(getConfigurationsMethod, polarisCircuitBreakerFactory); - Assertions.assertNotNull(values); + assertThat(values).isNotNull(); - Assertions.assertEquals(1, values.size()); + assertThat(values.size()).isEqualTo(1); Utils.sleepUninterrupted(10 * 1000); - Assertions.assertEquals(0, values.size()); + assertThat(values.size()).isEqualTo(0); }); } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCodeTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCodeTest.java index 41f65317b..773b57d19 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCodeTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCodeTest.java @@ -19,13 +19,14 @@ package com.tencent.cloud.polaris.circuitbreaker.common; import java.lang.reflect.Method; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.web.reactive.function.client.WebClientResponseException; +import static org.assertj.core.api.Assertions.assertThat; + /** * Test for ${@link PolarisResultToErrorCode}. * @@ -38,7 +39,7 @@ class PolarisResultToErrorCodeTest { @Test void testOnSuccess() { - Assertions.assertEquals(200, converter.onSuccess("any value")); + assertThat(converter.onSuccess("any value")).isEqualTo(200); } @Test @@ -51,7 +52,7 @@ class PolarisResultToErrorCodeTest { int errorCode = converter.onError(exception); // Then - Assertions.assertEquals(404, errorCode); + assertThat(errorCode).isEqualTo(404); } @Test @@ -60,7 +61,7 @@ class PolarisResultToErrorCodeTest { int errorCode = converter.onError(new RuntimeException("test")); // Then - Assertions.assertEquals(-1, errorCode); + assertThat(errorCode).isEqualTo(-1); } @Test @@ -72,7 +73,7 @@ class PolarisResultToErrorCodeTest { int errorCode = converter.onError(exception); // Then - Assertions.assertEquals(-1, errorCode); + assertThat(errorCode).isEqualTo(-1); } @Test @@ -85,10 +86,10 @@ class PolarisResultToErrorCodeTest { // test exist class boolean result1 = (boolean) checkClassExist.invoke(converter, "java.lang.String"); - Assertions.assertTrue(result1); + assertThat(result1).isTrue(); // test not exist class boolean result2 = (boolean) checkClassExist.invoke(converter, "com.nonexistent.Class"); - Assertions.assertFalse(result2); + assertThat(result2).isFalse(); } } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerInvocationHandlerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerInvocationHandlerTest.java index d95e5bf73..221f275fd 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerInvocationHandlerTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerInvocationHandlerTest.java @@ -28,7 +28,6 @@ import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; import feign.InvocationHandlerFactory; import feign.Target; import feign.codec.Decoder; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -37,6 +36,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.cloud.openfeign.FallbackFactory; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -84,20 +84,20 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest { @Test void testConstructorWithNullTarget() { - Assertions.assertThrows(NullPointerException.class, () -> + assertThatThrownBy(() -> new PolarisFeignCircuitBreakerInvocationHandler( null, dispatch, fallbackFactory, decoder ) - ); + ).isExactlyInstanceOf(NullPointerException.class); } @Test void testConstructorWithNullDispatch() { - Assertions.assertThrows(NullPointerException.class, () -> + assertThatThrownBy(() -> new PolarisFeignCircuitBreakerInvocationHandler( target, null, fallbackFactory, decoder ) - ); + ).isExactlyInstanceOf(NullPointerException.class); } @Test @@ -108,9 +108,9 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest { Map result = PolarisFeignCircuitBreakerInvocationHandler.toFallbackMethod(testDispatch); - Assertions.assertNotNull(result); - Assertions.assertTrue(result.containsKey(method)); - Assertions.assertEquals(method, result.get(method)); + assertThat(result).isNotNull(); + assertThat(result.containsKey(method)).isTrue(); + assertThat(result.get(method)).isEqualTo(method); } @Test @@ -119,10 +119,10 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest { Object mockProxy = mock(Object.class); // Test equals with null - Assertions.assertFalse((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {null})); + assertThat((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {null})).isFalse(); // Test equals with non-proxy object - Assertions.assertFalse((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {new Object()})); + assertThat((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {new Object()})).isFalse(); } @Test @@ -131,7 +131,7 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest { Object mockProxy = mock(Object.class); when(target.toString()).thenReturn("TestTarget"); - Assertions.assertEquals("TestTarget", handler.invoke(mockProxy, toStringMethod, null)); + assertThat(handler.invoke(mockProxy, toStringMethod, null)).isEqualTo("TestTarget"); } @Test @@ -178,7 +178,7 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest { Object result = handler.invoke(null, testMethod, new Object[] {}); // Verify - Assertions.assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test @@ -190,8 +190,8 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest { decoder ); - Assertions.assertEquals(handler, testHandler); - Assertions.assertEquals(handler.hashCode(), testHandler.hashCode()); + assertThat(testHandler).isEqualTo(handler); + assertThat(testHandler.hashCode()).isEqualTo(handler.hashCode()); } interface TestInterface { diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerTest.java index 46be28f21..ad9bdccb5 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerTest.java @@ -20,12 +20,12 @@ package com.tencent.cloud.polaris.circuitbreaker.instrument.feign; import feign.Feign; import feign.RequestLine; import feign.Target; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.cloud.openfeign.FallbackFactory; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -45,7 +45,7 @@ public class PolarisFeignCircuitBreakerTest { @Test public void testBuilderNotNull() { - Assertions.assertNotNull(builder); + assertThat(builder).isNotNull(); } @Test @@ -67,8 +67,8 @@ public class PolarisFeignCircuitBreakerTest { MyService result = builder.target(target, fallback); // Verify that the result is not null and the fallback factory is used - Assertions.assertNotNull(result); - Assertions.assertEquals("Fallback Hello", result.sayHello()); + assertThat(result).isNotNull(); + assertThat(result.sayHello()).isEqualTo("Fallback Hello"); } @Test @@ -93,8 +93,8 @@ public class PolarisFeignCircuitBreakerTest { MyService result = builder.target(target, fallbackFactory); // Verify that the result is not null and the fallback factory is used - Assertions.assertNotNull(result); - Assertions.assertEquals("Fallback Hello", result.sayHello()); + assertThat(result).isNotNull(); + assertThat(result.sayHello()).isEqualTo("Fallback Hello"); } @Test @@ -112,8 +112,7 @@ public class PolarisFeignCircuitBreakerTest { MyService result = builder.target(target); // Verify that the result is not null - Assertions.assertNotNull(result); - // Additional verifications can be added here based on the implementation + assertThat(result).isNotNull(); } @Test @@ -122,8 +121,7 @@ public class PolarisFeignCircuitBreakerTest { Feign feign = builder.build(null); // Verify that the Feign instance is not null - Assertions.assertNotNull(feign); - // Additional verifications can be added here based on the implementation + assertThat(feign).isNotNull(); } public interface MyService { diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisCircuitBreakerHttpResponseTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisCircuitBreakerHttpResponseTest.java index 152c86e7c..dd2f60739 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisCircuitBreakerHttpResponseTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisCircuitBreakerHttpResponseTest.java @@ -17,16 +17,19 @@ package com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate; +import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import com.tencent.polaris.api.pojo.CircuitBreakerStatus; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; + /** * Tests for {@link PolarisCircuitBreakerHttpResponse}. * @@ -35,28 +38,28 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) public class PolarisCircuitBreakerHttpResponseTest { @Test - void testConstructorWithCodeOnly() { + void testConstructorWithCodeOnly() throws IOException { PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200); - Assertions.assertEquals(200, response.getRawStatusCode()); - Assertions.assertNotNull(response.getHeaders()); - Assertions.assertTrue(response.getHeaders().isEmpty()); - Assertions.assertNull(response.getBody()); + assertThat(response.getStatusCode().value()).isEqualTo(200); + assertThat(response.getHeaders()).isNotNull(); + assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getBody()).isNull(); } @Test - void testConstructorWithCodeAndBody() { + void testConstructorWithCodeAndBody() throws IOException { String body = "test body"; PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200, body); - Assertions.assertEquals(200, response.getRawStatusCode()); - Assertions.assertNotNull(response.getHeaders()); - Assertions.assertTrue(response.getHeaders().isEmpty()); - Assertions.assertNotNull(response.getBody()); + assertThat(response.getStatusCode().value()).isEqualTo(200); + assertThat(response.getHeaders()).isNotNull(); + assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getBody()).isNotNull(); } @Test - void testConstructorWithCodeHeadersAndBody() { + void testConstructorWithCodeHeadersAndBody() throws IOException { String body = "test body"; Map headers = new HashMap<>(); headers.put("Content-Type", "application/json"); @@ -64,59 +67,59 @@ public class PolarisCircuitBreakerHttpResponseTest { PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200, headers, body); - Assertions.assertEquals(200, response.getRawStatusCode()); - Assertions.assertNotNull(response.getHeaders()); - Assertions.assertEquals(2, response.getHeaders().size()); - Assertions.assertTrue(response.getHeaders().containsKey("Content-Type")); - Assertions.assertTrue(response.getHeaders().containsKey("Authorization")); - Assertions.assertNotNull(response.getBody()); + assertThat(response.getStatusCode().value()).isEqualTo(200); + assertThat(response.getHeaders()).isNotNull(); + assertThat(response.getHeaders().size()).isEqualTo(2); + assertThat(response.getHeaders()).containsKey("Content-Type"); + assertThat(response.getHeaders()).containsKey("Authorization"); + assertThat(response.getBody()).isNotNull(); } @Test - void testConstructorWithFallbackInfo() { + void testConstructorWithFallbackInfo() throws IOException { Map headers = new HashMap<>(); headers.put("Content-Type", "application/json"); CircuitBreakerStatus.FallbackInfo fallbackInfo = new CircuitBreakerStatus.FallbackInfo(200, headers, "test body"); PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(fallbackInfo); - Assertions.assertEquals(200, response.getRawStatusCode()); - Assertions.assertEquals(fallbackInfo, response.getFallbackInfo()); - Assertions.assertNotNull(response.getHeaders()); - Assertions.assertTrue(response.getHeaders().containsKey("Content-Type")); - Assertions.assertNotNull(response.getBody()); + assertThat(response.getStatusCode().value()).isEqualTo(200); + assertThat(response.getFallbackInfo()).isEqualTo(fallbackInfo); + assertThat(response.getHeaders()).isNotNull(); + assertThat(response.getHeaders()).containsKey("Content-Type"); + assertThat(response.getBody()).isNotNull(); } @Test void testGetStatusTextWithValidHttpStatus() { PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200); - Assertions.assertEquals("OK", response.getStatusText()); + assertThat(response.getStatusText()).isEqualTo("OK"); } @Test void testGetStatusTextWithInvalidHttpStatus() { PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(999); - Assertions.assertEquals("", response.getStatusText()); + assertThat(response.getStatusText()).isEqualTo(""); } @Test void testClose() { PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200, "test body"); InputStream body = response.getBody(); - Assertions.assertNotNull(body); + assertThat(body).isNotNull(); response.close(); // Verify that reading from closed stream throws exception - Assertions.assertDoesNotThrow(() -> body.read()); + assertThatNoException().isThrownBy(() -> body.read()); } @Test void testCloseWithNullBody() { PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200); - Assertions.assertNull(response.getBody()); + assertThat(response.getBody()).isNull(); // Should not throw exception when closing null body - Assertions.assertDoesNotThrow(() -> response.close()); + assertThatNoException().isThrownBy(() -> response.close()); } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java index 57f676b86..375efb87b 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java @@ -33,7 +33,6 @@ import com.tencent.cloud.polaris.context.config.PolarisContextProperties; import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.api.core.ConfigKVFile; import com.tencent.polaris.configuration.client.internal.RevisableConfigFileGroup; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -354,21 +353,20 @@ public class PolarisConfigFileLocatorTest { testProperties.setCheckAddress(checkAddress); testProperties.setShutdownIfConnectToConfigServerFailed(shutdownIfConnectToConfigServerFailed); - Assertions.assertEquals(enabled, testProperties.isEnabled()); - Assertions.assertEquals(address, testProperties.getAddress()); - Assertions.assertEquals(port, testProperties.getPort()); - Assertions.assertEquals(token, testProperties.getToken()); - Assertions.assertEquals(autoRefresh, testProperties.isAutoRefresh()); - Assertions.assertEquals(refreshType, testProperties.getRefreshType()); - Assertions.assertEquals(groups, testProperties.getGroups()); - Assertions.assertEquals(preference, testProperties.isPreference()); - Assertions.assertEquals(dataSource, testProperties.getDataSource()); - Assertions.assertEquals(localFileRootPath, testProperties.getLocalFileRootPath()); - Assertions.assertEquals(internalEnabled, testProperties.isInternalEnabled()); - Assertions.assertEquals(checkAddress, testProperties.isCheckAddress()); - Assertions.assertEquals(shutdownIfConnectToConfigServerFailed, testProperties.isShutdownIfConnectToConfigServerFailed()); - - Assertions.assertNotNull(testProperties.toString()); + assertThat(testProperties.isEnabled()).isEqualTo(enabled); + assertThat(testProperties.getAddress()).isEqualTo(address); + assertThat(testProperties.getPort()).isEqualTo(port); + assertThat(testProperties.getToken()).isEqualTo(token); + assertThat(testProperties.isAutoRefresh()).isEqualTo(autoRefresh); + assertThat(testProperties.getRefreshType()).isEqualTo(refreshType); + assertThat(testProperties.getGroups()).isEqualTo(groups); + assertThat(testProperties.isPreference()).isEqualTo(preference); + assertThat(testProperties.getDataSource()).isEqualTo(dataSource); + assertThat(testProperties.getLocalFileRootPath()).isEqualTo(localFileRootPath); + assertThat(testProperties.isInternalEnabled()).isEqualTo(internalEnabled); + assertThat(testProperties.isCheckAddress()).isEqualTo(checkAddress); + assertThat(testProperties.isShutdownIfConnectToConfigServerFailed()).isEqualTo(shutdownIfConnectToConfigServerFailed); + assertThat(testProperties.toString()).isNotNull(); } private void clearCompositePropertySourceCache() { diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/RouterBootstrapAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/RouterBootstrapAutoConfigurationTest.java index ae61c2882..189d527ec 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/RouterBootstrapAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/RouterBootstrapAutoConfigurationTest.java @@ -26,7 +26,6 @@ import com.tencent.polaris.api.config.consumer.ServiceRouterConfig; import com.tencent.polaris.factory.ConfigAPIFactory; import com.tencent.polaris.factory.config.ConfigurationImpl; import com.tencent.polaris.plugins.router.nearby.NearbyRouterConfig; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -60,7 +59,7 @@ public class RouterBootstrapAutoConfigurationTest { routerConfigModifier.modify((ConfigurationImpl) configuration); NearbyRouterConfig nearbyRouterConfig = configuration.getConsumer().getServiceRouter().getPluginConfig( ServiceRouterConfig.DEFAULT_ROUTER_NEARBY, NearbyRouterConfig.class); - Assertions.assertEquals("CAMPUS", nearbyRouterConfig.getMatchLevel().name()); + assertThat(nearbyRouterConfig.getMatchLevel().name()).isEqualTo("CAMPUS"); }); } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java index 3ed9fe45b..771dacb1b 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java @@ -54,7 +54,18 @@ public final class MetadataConstant { * Metadata HTTP header name. */ public static class HeaderName { - + /** + * TSF Tags. + */ + public static final String TSF_TAGS = "TSF-Tags"; + /** + * TSF System Tags. + */ + public static final String TSF_SYSTEM_TAG = "TSF-System-Tags"; + /** + * TSF Metadata. + */ + public static final String TSF_METADATA = "TSF-Metadata"; /** * Custom metadata. */ @@ -78,6 +89,11 @@ public final class MetadataConstant { * Namespace context. */ public static final String NAMESPACE = "SCT-NAMESPACE"; + + /** + * TSF Namespace Id context. + */ + public static final String TSF_NAMESPACE_ID = "TSF-NamespaceId"; } public static class DefaultMetadata { diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java index 8eb0edf8f..9c7f9fda4 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java @@ -51,7 +51,7 @@ public class OrderConstant { /** * Order of encode transfer metadata interceptor. */ - public static final int ENCODE_TRANSFER_METADATA_INTERCEPTOR_ORDER = Ordered.LOWEST_PRECEDENCE - 1; + public static final int ENCODE_TRANSFER_METADATA_INTERCEPTOR_ORDER = Ordered.LOWEST_PRECEDENCE; /** * Order of encode router label interceptor. diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/tsf/TsfContextUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/tsf/TsfContextUtils.java index bde215cbb..5d2ece26e 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/tsf/TsfContextUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/tsf/TsfContextUtils.java @@ -77,4 +77,12 @@ public final class TsfContextUtils { } return onlyTsfConsulEnabled; } + + /** + * This method should be called after {@link com.tencent.cloud.common.tsf.TsfContextUtils#isOnlyTsfConsulEnabled(Environment)}. + * @return whether only Tsf Consul is enabled + */ + public static boolean isOnlyTsfConsulEnabled() { + return onlyTsfConsulEnabled; + } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java index 165d7b1e9..905490577 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java @@ -17,11 +17,15 @@ package com.tencent.cloud.common.util; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import org.slf4j.Logger; @@ -93,6 +97,30 @@ public final class JacksonUtils { } } + public static T deserialize(String jsonStr, TypeReference typeReference) { + try { + return OM.readValue(jsonStr, typeReference); + } + catch (JsonProcessingException e) { + LOG.error("Json to object failed. {}", typeReference, e); + throw new RuntimeException("Json to object failed.", e); + } + } + + public static List deserializeCollection(String jsonArrayStr, Class clazz) { + JavaType javaType = getCollectionType(ArrayList.class, clazz); + try { + return (List) OM.readValue(jsonArrayStr, javaType); + } + catch (Exception t) { + throw new RuntimeException(t); + } + } + + public static JavaType getCollectionType(Class collectionClass, Class... elementClasses) { + return OM.getTypeFactory().constructParametricType(collectionClass, elementClasses); + } + /** * Json to Map. * @param jsonStr Json String diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/TsfTagUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/TsfTagUtils.java new file mode 100644 index 000000000..fb89182cf --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/TsfTagUtils.java @@ -0,0 +1,231 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.common.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import com.tencent.cloud.common.constant.MetadataConstant; +import com.tencent.cloud.common.tsf.TsfContextUtils; +import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.polaris.metadata.core.constant.MetadataConstants; +import com.tencent.polaris.metadata.core.constant.TsfMetadataConstants; +import com.tencent.polaris.plugins.connector.consul.service.common.TagConstant; +import com.tencent.polaris.plugins.router.lane.LaneRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.tsf.core.entity.Metadata; +import org.springframework.tsf.core.entity.Tag; + +public final class TsfTagUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(TsfTagUtils.class); + + private TsfTagUtils() { + } + + public static List deserializeTagList(String buffer) { + if (StringUtils.isEmpty(buffer)) { + return null; + } + return Arrays.asList(JacksonUtils.deserialize(UrlUtils.decode(buffer), Tag[].class)); + } + + public static Metadata deserializeMetadata(String buffer) { + if (StringUtils.isEmpty(buffer)) { + return null; + } + return JacksonUtils.deserialize(UrlUtils.decode(buffer), Metadata.class); + } + + public static Map getTsfMetadataMap(Map calleeTransitiveHeaders, Map disposableMetadata, + Map customMetadata, Map applicationMetadata) { + + Map tsfMetadataMap = new HashMap<>(); + Set tsfUserTagKeys = new HashSet<>(); + // user tags + List tsfUserTags = new ArrayList<>(); + List tsfSystemTags = new ArrayList<>(); + Tag laneTag = null; + + for (Map.Entry entry : customMetadata.entrySet()) { + if (tsfUserTagKeys.contains(entry.getKey())) { + continue; + } + Tag tag = new Tag(entry.getKey(), entry.getValue(), Tag.ControlFlag.TRANSITIVE); + tsfUserTags.add(tag); + tsfUserTagKeys.add(entry.getKey()); + if (LaneRouter.TRAFFIC_STAIN_LABEL.equals(entry.getKey()) && entry.getValue().contains("/")) { + String lane = entry.getValue().split("/")[1]; + laneTag = new Tag("lane", lane, Tag.ControlFlag.TRANSITIVE); + } + } + for (Map.Entry entry : disposableMetadata.entrySet()) { + if (tsfUserTagKeys.contains(entry.getKey())) { + continue; + } + Tag tag = new Tag(entry.getKey(), entry.getValue()); + tsfUserTags.add(tag); + tsfUserTagKeys.add(entry.getKey()); + } + + for (Map.Entry entry : calleeTransitiveHeaders.entrySet()) { + if (entry.getKey().startsWith(MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX)) { + String key = entry.getKey().substring(MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH); + String value = entry.getValue(); + if (tsfUserTagKeys.contains(key)) { + continue; + } + + Tag tag = new Tag(key, value, Tag.ControlFlag.TRANSITIVE); + tsfUserTags.add(tag); + tsfUserTagKeys.add(key); + if (LaneRouter.TRAFFIC_STAIN_LABEL.equals(key) && value.contains("/")) { + String lane = value.split("/")[1]; + laneTag = new Tag("lane", lane, Tag.ControlFlag.TRANSITIVE); + + } + } + } + + if (laneTag != null) { + tsfSystemTags.add(laneTag); + } + + if (CollectionUtils.isNotEmpty(tsfUserTags)) { + tsfMetadataMap.put(MetadataConstant.HeaderName.TSF_TAGS, JacksonUtils.serialize2Json(tsfUserTags)); + } + + if (CollectionUtils.isNotEmpty(tsfSystemTags)) { + tsfMetadataMap.put(MetadataConstant.HeaderName.TSF_SYSTEM_TAG, JacksonUtils.serialize2Json(tsfSystemTags)); + } + + Metadata metadata = new Metadata(); + for (Map.Entry entry : applicationMetadata.entrySet()) { + switch (entry.getKey()) { + case MetadataConstants.LOCAL_SERVICE: + metadata.setServiceName(entry.getValue()); + break; + case MetadataConstants.LOCAL_IP: + metadata.setLocalIp(entry.getValue()); + break; + case TsfMetadataConstants.TSF_GROUP_ID: + metadata.setGroupId(entry.getValue()); + break; + case TsfMetadataConstants.TSF_APPLICATION_ID: + metadata.setApplicationId(entry.getValue()); + break; + case TsfMetadataConstants.TSF_INSTNACE_ID: + metadata.setInstanceId(entry.getValue()); + break; + case TsfMetadataConstants.TSF_PROG_VERSION: + metadata.setApplicationVersion(entry.getValue()); + break; + case TsfMetadataConstants.TSF_NAMESPACE_ID: + metadata.setNamespaceId(entry.getValue()); + break; + } + } + tsfMetadataMap.put(MetadataConstant.HeaderName.TSF_METADATA, JacksonUtils.serialize2Json(metadata)); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("calleeTransitiveHeaders:{}, disposableMetadata: {}, customMetadata: {}, applicationMetadata: {}, tsfMetadataMap:{}", + calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata, tsfMetadataMap); + } + + return tsfMetadataMap; + } + + public static void updateTsfMetadata(Map mergedTransitiveMetadata, + Map mergedDisposableMetadata, Map mergedApplicationMetadata, Map addHeaders, + AtomicReference callerIp, String encodedUserTagList, String encodedSystemTagList, String encodedMetadata) { + + if (!TsfContextUtils.isOnlyTsfConsulEnabled()) { + return; + } + List tsfUserTagList = TsfTagUtils.deserializeTagList(encodedUserTagList); + int tagSize = Optional.ofNullable(tsfUserTagList).map(List::size).orElse(0); + Map tsfTransitiveMetadata = new HashMap<>(tagSize); + Map tsfDisposableMetadata = new HashMap<>(tagSize); + if (CollectionUtils.isNotEmpty(tsfUserTagList)) { + for (Tag tag : tsfUserTagList) { + if (Tag.ControlFlag.TRANSITIVE.equals(tag.getFlags())) { + tsfTransitiveMetadata.put(tag.getKey(), tag.getValue()); + } + tsfDisposableMetadata.put(tag.getKey(), tag.getValue()); + } + mergedTransitiveMetadata.putAll(tsfTransitiveMetadata); + mergedDisposableMetadata.putAll(tsfDisposableMetadata); + } + + List tsfSystemTagList = TsfTagUtils.deserializeTagList(encodedSystemTagList); + if (CollectionUtils.isNotEmpty(tsfSystemTagList)) { + for (Tag tag : tsfSystemTagList) { + if ("lane".equals(tag.getKey())) { + mergedTransitiveMetadata.put(LaneRouter.TRAFFIC_STAIN_LABEL, UrlUtils.encode("tsf/" + tag.getValue())); + addHeaders.put(MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX + LaneRouter.TRAFFIC_STAIN_LABEL, + UrlUtils.encode("tsf/" + tag.getValue())); + } + } + } + + Metadata metadata = TsfTagUtils.deserializeMetadata(encodedMetadata); + if (metadata != null) { + if (StringUtils.isNotEmpty(metadata.getLocalIp())) { + callerIp.set(metadata.getLocalIp()); + } + if (StringUtils.isNotEmpty(metadata.getApplicationId())) { + mergedApplicationMetadata.put(TsfMetadataConstants.TSF_APPLICATION_ID, metadata.getApplicationId()); + } + if (StringUtils.isNotEmpty(metadata.getGroupId())) { + mergedApplicationMetadata.put(TsfMetadataConstants.TSF_GROUP_ID, metadata.getGroupId()); + } + if (StringUtils.isNotEmpty(metadata.getApplicationVersion())) { + mergedApplicationMetadata.put(TsfMetadataConstants.TSF_PROG_VERSION, metadata.getApplicationVersion()); + } + if (StringUtils.isNotEmpty(metadata.getNamespaceId())) { + mergedApplicationMetadata.put(TsfMetadataConstants.TSF_NAMESPACE_ID, metadata.getNamespaceId()); + } + if (StringUtils.isNotEmpty(metadata.getServiceName())) { + mergedApplicationMetadata.put(MetadataConstants.LOCAL_SERVICE, metadata.getServiceName()); + mergedApplicationMetadata.put(TagConstant.SYSTEM_FIELD.SOURCE_SERVICE_NAME, metadata.getServiceName()); + } + if (StringUtils.isNotEmpty(metadata.getLocalIp())) { + mergedApplicationMetadata.put(MetadataConstants.LOCAL_IP, metadata.getLocalIp()); + } + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("mergedTransitiveMetadata:{}, mergedDisposableMetadata: {}, mergedApplicationMetadata: {}," + + " addHeaders:{}, encodedUserTagList:{}, encodedSystemTagList:{}, encodedMetadata:{}, callerIp:{}", + mergedTransitiveMetadata, mergedDisposableMetadata, mergedApplicationMetadata, + addHeaders, encodedUserTagList, encodedSystemTagList, encodedMetadata, callerIp.get()); + } + + } + +} diff --git a/spring-cloud-tencent-commons/src/main/java/org/springframework/tsf/core/entity/Metadata.java b/spring-cloud-tencent-commons/src/main/java/org/springframework/tsf/core/entity/Metadata.java new file mode 100644 index 000000000..66cfa3c12 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/org/springframework/tsf/core/entity/Metadata.java @@ -0,0 +1,152 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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 org.springframework.tsf.core.entity; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.springframework.util.StringUtils; + + +public class Metadata implements Serializable { + + @JsonProperty("ai") + private String applicationId = ""; + + @JsonProperty("av") + private String applicationVersion = ""; + + @JsonProperty("sn") + private String serviceName = ""; + + @JsonProperty("ii") + private String instanceId = ""; + + @JsonProperty("gi") + private String groupId = ""; + + @JsonProperty("li") + private String localIp = ""; + + @JsonProperty("lis") + private String localIps = ""; + + @JsonProperty("ni") + private String namespaceId = ""; + + @JsonProperty("pi") + private boolean preferIpv6; + + public Metadata() { + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + // 其实是程序包的版本,但是这里程序包概念没啥用,直接用应用来表示 + public String getApplicationVersion() { + return applicationVersion; + } + + public void setApplicationVersion(String applicationVersion) { + this.applicationVersion = applicationVersion; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getInstanceId() { + return instanceId; + } + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getLocalIp() { + if (preferIpv6 && !StringUtils.isEmpty(localIps)) { + for (String ip : localIps.split(",")) { + if (ip.contains(":")) { + return ip; + } + } + } + return localIp; + } + + public void setLocalIp(String localIp) { + this.localIp = localIp; + } + + public String getLocalIps() { + return localIps; + } + + public void setLocalIps(String localIps) { + this.localIps = localIps; + } + + public String getNamespaceId() { + return namespaceId; + } + + public void setNamespaceId(String namespaceId) { + this.namespaceId = namespaceId; + } + + public boolean isPreferIpv6() { + return preferIpv6; + } + + public void setPreferIpv6(boolean preferIpv6) { + this.preferIpv6 = preferIpv6; + } + + @Override + public String toString() { + return "Metadata{" + + "applicationId='" + applicationId + '\'' + + ", applicationVersion='" + applicationVersion + '\'' + + ", serviceName='" + serviceName + '\'' + + ", instanceId='" + instanceId + '\'' + + ", groupId='" + groupId + '\'' + + ", localIp='" + localIp + '\'' + + ", namespaceId='" + namespaceId + '\'' + + '}'; + } + +} diff --git a/spring-cloud-tencent-commons/src/main/java/org/springframework/tsf/core/entity/Tag.java b/spring-cloud-tencent-commons/src/main/java/org/springframework/tsf/core/entity/Tag.java index be624bbab..49faf9282 100644 --- a/spring-cloud-tencent-commons/src/main/java/org/springframework/tsf/core/entity/Tag.java +++ b/spring-cloud-tencent-commons/src/main/java/org/springframework/tsf/core/entity/Tag.java @@ -22,17 +22,19 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import com.fasterxml.jackson.annotation.JsonProperty; + public class Tag implements Serializable { /** * update version whenever change the content in tag. */ public static final int VERSION = 1; - + @JsonProperty("k") private String key; - + @JsonProperty("v") private String value; - + @JsonProperty("f") private Set flags = new HashSet<>(); public Tag(String key, String value, ControlFlag... flags) { @@ -98,31 +100,37 @@ public class Tag implements Serializable { /** * tag transitive by all services. */ + @JsonProperty("0") TRANSITIVE, /** * tag not used in auth. */ + @JsonProperty("1") NOT_IN_AUTH, /** * tag not used in route. */ + @JsonProperty("2") NOT_IN_ROUTE, /** * tag not used in trace. */ + @JsonProperty("3") NOT_IN_SLEUTH, /** * tag not used in lane. */ + @JsonProperty("4") NOT_IN_LANE, /** * tag not used in unit. */ + @JsonProperty("5") IN_UNIT } } diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/TsfTagUtilsTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/TsfTagUtilsTest.java new file mode 100644 index 000000000..341b70227 --- /dev/null +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/TsfTagUtilsTest.java @@ -0,0 +1,43 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.common.util; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.tsf.core.entity.Tag; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * test for {@link TsfTagUtils}. + */ +public class TsfTagUtilsTest { + + @Test + public void deserializeTagList() { + String data = "%5B%7B%22k%22%3A%22tsf-gateway-ratelimit-context%22%2C%22v%22%3A%22grp-vyiwvq5t%22%2C%22f%22%3A%5B%5D%7D%2C%7B%22k%22%3A%22feat%22%2C%22v%22%3A%22test%22%2C%22f%22%3A%5B%220%22%5D%7D%5D"; + List tagList = TsfTagUtils.deserializeTagList(data); + for (Tag tag : tagList) { + assertThat(tag.getKey()).isNotNull(); + assertThat(tag.getValue()).isNotNull(); + assertThat(tag.getFlags()).isNotNull(); + } + } +} diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index d1ab4f51c..58536b73f 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -74,7 +74,7 @@ 2.1.0.0-2021.0.9-SNAPSHOT - 2.0.2.0 + 2.0.3.0-SNAPSHOT 1.78.1 diff --git a/spring-cloud-tencent-examples/tsf-example/msgw-scg/pom.xml b/spring-cloud-tencent-examples/tsf-example/msgw-scg/pom.xml new file mode 100644 index 000000000..135d28ea2 --- /dev/null +++ b/spring-cloud-tencent-examples/tsf-example/msgw-scg/pom.xml @@ -0,0 +1,32 @@ + + + + tsf-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + msgw-scg + + + + + com.tencent.cloud + spring-cloud-starter-tencent-all + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/java/com/tencent/cloud/tsf/msgw/scg/ScgApplication.java b/spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/java/com/tencent/cloud/tsf/msgw/scg/ScgApplication.java new file mode 100644 index 000000000..bdb9766d8 --- /dev/null +++ b/spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/java/com/tencent/cloud/tsf/msgw/scg/ScgApplication.java @@ -0,0 +1,34 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.msgw.scg; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.tsf.annotation.EnableTsf; + +/** + * @author seanlxliu + */ +@SpringBootApplication +@EnableTsf +public class ScgApplication { + + public static void main(String[] args) { + SpringApplication.run(ScgApplication.class, args); + } +} diff --git a/spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..6f4b1c4c2 --- /dev/null +++ b/spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/resources/bootstrap.yml @@ -0,0 +1,28 @@ +server: + port: 8080 + error: + include-exception: true +spring: + application: + name: msgw-scg + cloud: + gateway: + discovery: + locator: + enabled: true + lower-case-service-id: false + httpclient: + # The connect timeout in millis, the default is 45s. + connectTimeout: 200 + responseTimeout: 10s + consul: + enabled: true + scheme: HTTP + +logging: + level: + root: INFO + file: + name: /tsf-demo-logs/${spring.application.name}/root.log + pattern: + level: "%-5level [${spring.application.name},%mdc{trace_id},%mdc{span_id},]" diff --git a/spring-cloud-tencent-examples/tsf-example/pom.xml b/spring-cloud-tencent-examples/tsf-example/pom.xml index 979efb5f9..64bb8a42a 100644 --- a/spring-cloud-tencent-examples/tsf-example/pom.xml +++ b/spring-cloud-tencent-examples/tsf-example/pom.xml @@ -17,5 +17,6 @@ provider-demo consumer-demo-retry consumer-demo + msgw-scg diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/GatewayPluginAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/GatewayPluginAutoConfiguration.java index ef3c6805f..dd40ab118 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/GatewayPluginAutoConfiguration.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/GatewayPluginAutoConfiguration.java @@ -17,22 +17,34 @@ package com.tencent.cloud.plugin.gateway; +import com.tencent.cloud.common.tsf.ConditionalOnOnlyTsfConsulEnabled; import com.tencent.cloud.plugin.gateway.context.ContextGatewayFilterFactory; import com.tencent.cloud.plugin.gateway.context.ContextGatewayProperties; import com.tencent.cloud.plugin.gateway.context.ContextGatewayPropertiesManager; import com.tencent.cloud.plugin.gateway.context.ContextPropertiesRouteDefinitionLocator; import com.tencent.cloud.plugin.gateway.context.ContextRoutePredicateFactory; import com.tencent.cloud.plugin.gateway.context.GatewayConfigChangeListener; +import com.tencent.cloud.plugin.gateway.context.GatewayConsulRepo; import com.tencent.cloud.polaris.config.ConditionalOnPolarisConfigEnabled; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; +import com.tencent.cloud.polaris.context.PolarisSDKContextManager; import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient; import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient; +import com.tencent.tsf.gateway.core.plugin.GatewayPluginFactory; +import com.tencent.tsf.gateway.core.plugin.JwtGatewayPlugin; +import com.tencent.tsf.gateway.core.plugin.OAuthGatewayPlugin; +import com.tencent.tsf.gateway.core.plugin.ReqTransformerGatewayPlugin; +import com.tencent.tsf.gateway.core.plugin.TagGatewayPlugin; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.gateway.config.GatewayAutoConfiguration; import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; @@ -40,6 +52,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; +import static com.tencent.tsf.gateway.core.constant.PluginType.JWT; +import static com.tencent.tsf.gateway.core.constant.PluginType.OAUTH; +import static com.tencent.tsf.gateway.core.constant.PluginType.REQ_TRANSFORMER; +import static com.tencent.tsf.gateway.core.constant.PluginType.TAG; + /** * Auto configuration for spring cloud gateway plugins. * @author lepdou 2022-07-06 @@ -52,6 +69,7 @@ public class GatewayPluginAutoConfiguration { @Configuration @ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.context.enabled", matchIfMissing = true) @ConditionalOnPolarisConfigEnabled + @AutoConfigureBefore(GatewayAutoConfiguration.class) @ConditionalOnClass(GlobalFilter.class) @Import(ContextGatewayProperties.class) public static class ContextPluginConfiguration { @@ -62,6 +80,9 @@ public class GatewayPluginAutoConfiguration { @Value("${spring.cloud.polaris.discovery.eager-load.gateway.enabled:#{'false'}}") private boolean gatewayEagerLoadEnabled; + @Value("${tsf_group_id:}") + private String tsfGroupId; + @Bean public ContextGatewayFilterFactory contextGatewayFilterFactory(ContextGatewayPropertiesManager contextGatewayPropertiesManager) { return new ContextGatewayFilterFactory(contextGatewayPropertiesManager); @@ -73,8 +94,8 @@ public class GatewayPluginAutoConfiguration { } @Bean - public ContextRoutePredicateFactory contextServiceRoutePredicateFactory() { - return new ContextRoutePredicateFactory(); + public ContextRoutePredicateFactory contextServiceRoutePredicateFactory(ContextGatewayPropertiesManager manager) { + return new ContextRoutePredicateFactory(manager); } @Bean @@ -82,7 +103,8 @@ public class GatewayPluginAutoConfiguration { @Autowired(required = false) PolarisDiscoveryClient polarisDiscoveryClient, @Autowired(required = false) PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) { ContextGatewayPropertiesManager contextGatewayPropertiesManager = new ContextGatewayPropertiesManager(); - contextGatewayPropertiesManager.setGroupRouteMap(properties.getGroups()); + contextGatewayPropertiesManager.refreshGroupRoute(properties.getGroups()); + contextGatewayPropertiesManager.setPathRewrites(properties.getPathRewrites()); if (commonEagerLoadEnabled && gatewayEagerLoadEnabled) { contextGatewayPropertiesManager.eagerLoad(polarisDiscoveryClient, polarisReactiveDiscoveryClient); } @@ -96,7 +118,7 @@ public class GatewayPluginAutoConfiguration { @Bean public GatewayConfigChangeListener gatewayConfigChangeListener(ContextGatewayPropertiesManager manager, - ApplicationEventPublisher publisher, Environment environment) { + ApplicationEventPublisher publisher, Environment environment) { return new GatewayConfigChangeListener(manager, publisher, environment); } @@ -105,5 +127,55 @@ public class GatewayPluginAutoConfiguration { ApplicationContext applicationContext) { return new PolarisReactiveLoadBalancerClientFilterBeanPostProcessor(applicationContext); } + + @Bean + @ConditionalOnOnlyTsfConsulEnabled + public GatewayConsulRepo gatewayConsulRepo(ContextGatewayProperties contextGatewayProperties, + PolarisSDKContextManager polarisSDKContextManager, + ContextGatewayPropertiesManager contextGatewayPropertiesManager, + ApplicationEventPublisher publisher) { + return new GatewayConsulRepo(contextGatewayProperties, polarisSDKContextManager, + contextGatewayPropertiesManager, publisher, tsfGroupId); + } + + @Bean(destroyMethod = "close") + @ConditionalOnMissingBean + public GatewayPluginFactory gatewayPluginFactory( + ReqTransformerGatewayPlugin reqTransformerGatewayPlugin, + OAuthGatewayPlugin oAuthGatewayPlugin, + JwtGatewayPlugin jwtGatewayPlugin, + TagGatewayPlugin tagGatewayPlugin) { + GatewayPluginFactory gatewayPluginFactory = new GatewayPluginFactory(); + gatewayPluginFactory.putGatewayPlugin(OAUTH.getType(), oAuthGatewayPlugin); + gatewayPluginFactory.putGatewayPlugin(JWT.getType(), jwtGatewayPlugin); + gatewayPluginFactory.putGatewayPlugin(TAG.getType(), tagGatewayPlugin); + gatewayPluginFactory.putGatewayPlugin(REQ_TRANSFORMER.getType(), reqTransformerGatewayPlugin); + return gatewayPluginFactory; + } + + @Bean + @ConditionalOnMissingBean + public TagGatewayPlugin tagGatewayPlugin() { + return new TagGatewayPlugin(); + } + + @Bean + @ConditionalOnMissingBean + public OAuthGatewayPlugin oAuthGatewayPlugin(LoadBalancerClientFactory clientFactory, PolarisSDKContextManager polarisSDKContextManager) { + return new OAuthGatewayPlugin(clientFactory, polarisSDKContextManager); + } + + @Bean + @ConditionalOnMissingBean + public JwtGatewayPlugin jwtGatewayPlugin() { + return new JwtGatewayPlugin(); + } + + @Bean + @ConditionalOnMissingBean + public ReqTransformerGatewayPlugin reqTransformerGatewayPlugin() { + return new ReqTransformerGatewayPlugin(); + } + } } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilter.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilter.java index 3a40b835e..ae719d97f 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilter.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilter.java @@ -33,7 +33,7 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance private final ReactiveLoadBalancerClientFilter clientFilter; public PolarisReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, - GatewayLoadBalancerProperties properties, ReactiveLoadBalancerClientFilter clientFilter) { + GatewayLoadBalancerProperties properties, ReactiveLoadBalancerClientFilter clientFilter) { super(clientFactory, properties); this.clientFilter = clientFilter; } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.java index bdf6f71b1..b6643aab7 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.java @@ -31,7 +31,7 @@ public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessor implements */ public static final int ORDER = 0; - private ApplicationContext applicationContext; + private final ApplicationContext applicationContext; public PolarisReactiveLoadBalancerClientFilterBeanPostProcessor(ApplicationContext applicationContext) { this.applicationContext = applicationContext; diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/AuthCheckUtil.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/AuthCheckUtil.java new file mode 100644 index 000000000..6bc6f6f3c --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/AuthCheckUtil.java @@ -0,0 +1,119 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Optional; + +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.tsf.gateway.core.constant.AuthMode; +import com.tencent.tsf.gateway.core.constant.CommonStatus; +import com.tencent.tsf.gateway.core.constant.HeaderName; +import com.tencent.tsf.gateway.core.constant.TsfAlgType; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; +import com.tencent.tsf.gateway.core.util.TsfSignUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.web.server.ServerWebExchange; + +public final class AuthCheckUtil { + + private static final Logger logger = LoggerFactory.getLogger(AuthCheckUtil.class); + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private AuthCheckUtil() { + } + + public static void signCheck(ServerWebExchange exchange, GroupContext groupContext) { + AuthMode authMode = Optional.ofNullable(groupContext.getAuth()).map(GroupContext.ContextAuth::getType) + .orElse(null); + if (!AuthMode.SECRET.equals(authMode)) { + return; + } + + String secretId = exchange.getRequest().getHeaders().getFirst(HeaderName.APP_KEY); + if (StringUtils.isEmpty(secretId)) { + logger.error("[signCheck] secretId is empty. path: {}", exchange.getRequest().getURI().getPath()); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_BAD_REQUEST, "RequestHeader x-mg-secretid is required"); + } + String nonce = exchange.getRequest().getHeaders().getFirst(HeaderName.NONCE); + if (StringUtils.isEmpty(nonce)) { + logger.error("[signCheck] nonce is empty. path: {}", exchange.getRequest().getURI().getPath()); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_BAD_REQUEST, "RequestHeader x-mg-nonce is required"); + } + String alg = exchange.getRequest().getHeaders().getFirst(HeaderName.ALG); + if (StringUtils.isEmpty(alg)) { + logger.error("[signCheck] alg is empty. path: {}", exchange.getRequest().getURI().getPath()); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_BAD_REQUEST, "RequestHeader x-mg-alg is required"); + } + String clientSign = exchange.getRequest().getHeaders().getFirst(HeaderName.SIGN); + if (StringUtils.isEmpty(clientSign)) { + logger.error("[signCheck] sign is empty. path: {}", exchange.getRequest().getURI().getPath()); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "RequestHeader x-mg-sign is wrong"); + } + + TsfAlgType algType = TsfAlgType.getSecurityCode(alg); + + List secretList = groupContext.getAuth().getSecrets(); + + GroupContext.ContextSecret secret = null; + if (secretList != null) { + for (GroupContext.ContextSecret s : secretList) { + if (s.getId().equals(secretId)) { + secret = s; + break; + } + } + } + + if (secret == null) { + logger.error("[signCheck] secret is not found. secretId:{}, path: {}", secretId, exchange.getRequest() + .getURI().getPath()); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "GroupSecret Not Found"); + } + if (CommonStatus.DISABLED.getStatus().equals(secret.getStatus())) { + logger.error("[signCheck] secret is invalid. secretId:{}, path: {}", secretId, exchange.getRequest() + .getURI().getPath()); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_BAD_REQUEST, "GroupSecret Invalid"); + } + + String expiredTime = secret.getExpiredTime(); + LocalDateTime expiredDateTime = LocalDateTime.parse(expiredTime, FORMATTER); + LocalDateTime nowDateTime = LocalDateTime.now(); + boolean expired = nowDateTime.isAfter(expiredDateTime); + + if (expired) { + logger.error("[signCheck] secret is expired. secretId:{}, path: {}", secretId, exchange.getRequest() + .getURI().getPath()); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "GroupSecret Expired"); + } + + + String serverSign = TsfSignUtil.generate(nonce, secretId, secret.getKey(), algType); + if (!StringUtils.equals(clientSign, serverSign)) { + logger.error("[signCheck] sign check failed. secretId:{}, nonce:{}, secretKey:{}, clientSign:{}, path: {}", + secretId, nonce, secret.getKey(), clientSign, exchange.getRequest().getURI().getPath()); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "GroupSecret Check Failed"); + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilter.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilter.java index fdb81daf4..66eb9caf9 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilter.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilter.java @@ -17,13 +17,34 @@ package com.tencent.cloud.plugin.gateway.context; +import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.net.URI; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.tsf.gateway.core.TsfGatewayRequest; +import com.tencent.tsf.gateway.core.constant.GatewayConstant; +import com.tencent.tsf.gateway.core.constant.HeaderName; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; +import com.tencent.tsf.gateway.core.model.PluginDetail; +import com.tencent.tsf.gateway.core.model.PluginInfo; +import com.tencent.tsf.gateway.core.model.PluginPayload; +import com.tencent.tsf.gateway.core.plugin.GatewayPluginFactory; +import com.tencent.tsf.gateway.core.plugin.IGatewayPlugin; +import com.tencent.tsf.gateway.core.util.CookieUtil; +import com.tencent.tsf.gateway.core.util.IdGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; @@ -34,20 +55,30 @@ import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.support.NotFoundException; import org.springframework.core.Ordered; +import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.tsf.core.TsfContext; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.UriComponentsBuilder; +import static com.tencent.tsf.gateway.core.constant.GatewayConstant.TSF_GATEWAY_RATELIMIT_CONTEXT_TAG; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; +import static org.springframework.http.server.PathContainer.parsePath; public class ContextGatewayFilter implements GatewayFilter, Ordered { private static final Logger logger = LoggerFactory.getLogger(ContextGatewayFilter.class); - private ContextGatewayPropertiesManager manager; + private final ContextGatewayPropertiesManager manager; - private ContextGatewayFilterFactory.Config config; + private final ContextGatewayFilterFactory.Config config; public ContextGatewayFilter(ContextGatewayPropertiesManager manager, ContextGatewayFilterFactory.Config config) { this.manager = manager; @@ -56,57 +87,196 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ResponseStatusException pathRewriteException = (ResponseStatusException) exchange.getAttributes() + .get(GatewayConstant.PATH_REWRITE_EXCEPTION); + if (pathRewriteException != null) { + throw pathRewriteException; + } + // plugins will add metadata + if (exchange.getAttributes().containsKey(MetadataConstant.HeaderName.METADATA_CONTEXT)) { + MetadataContextHolder.set((MetadataContext) exchange.getAttributes().get( + MetadataConstant.HeaderName.METADATA_CONTEXT)); + } + // for tsf gateway rate-limit + TsfContext.putTag(TSF_GATEWAY_RATELIMIT_CONTEXT_TAG, config.getGroup()); + GroupContext groupContext = manager.getGroups().get(config.getGroup()); + // auth + AuthCheckUtil.signCheck(exchange, groupContext); + + // path can be changed in ContextRoutePredicateFactory + PathContainer path = (PathContainer) exchange.getAttributes() + .computeIfAbsent(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR, + s -> parsePath(exchange.getRequest().getURI().getRawPath())); + + ServerWebExchange apiRebuildExchange; + if (ApiType.MS.equals(groupContext.getPredicate().getApiType())) { - return msFilter(exchange, chain, groupContext); + apiRebuildExchange = msFilter(exchange, chain, groupContext, path); } else { - return externalFilter(exchange, chain, groupContext); + apiRebuildExchange = externalFilter(exchange, chain, path); } + + ServerWebExchange pluginRebuildExchange = doPlugins(apiRebuildExchange, + (GroupContext.ContextRoute) exchange.getAttributes().get(GatewayConstant.CONTEXT_ROUTE)); + + return chain.filter(pluginRebuildExchange); + } - private Mono externalFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext) { + private ServerWebExchange doPlugins(ServerWebExchange exchange, GroupContext.ContextRoute contextRoute) { + List pluginPayloadList = pluginCheck(exchange, contextRoute); + if (CollectionUtils.isEmpty(pluginPayloadList)) { + return exchange; + } + + ServerHttpRequest.Builder builder = exchange.getRequest().mutate(); + HttpHeaders headers = exchange.getRequest().getHeaders(); + StringBuilder cookieStringBuilder = new StringBuilder(); + List cookie = headers.get("Cookie"); + if (!org.springframework.util.CollectionUtils.isEmpty(cookie)) { + cookieStringBuilder.append(cookie.get(0)); + } + + Map queryParamFromPlugin = new HashMap<>(); + Map headersMapFromPlugin = new HashMap<>(); + Map responseHeadersMapFromPlugin = new HashMap<>(); + //添加queryParam与header + for (PluginPayload pluginPayload : pluginPayloadList) { + Map parameterMap = pluginPayload.getParameterMap(); + Map headersMap = pluginPayload.getRequestHeaders(); + Map responseHeaderMap = pluginPayload.getResponseHeaders(); + Map requestCookieMap = pluginPayload.getRequestCookies(); + + //合并请求参数 + if (!org.springframework.util.CollectionUtils.isEmpty(parameterMap)) { + queryParamFromPlugin.putAll(parameterMap); + } + + //合并请求头参数 + if (!org.springframework.util.CollectionUtils.isEmpty(headersMap)) { + headersMapFromPlugin.putAll(headersMap); + } + //合并cookie参数 + CookieUtil.buildCookie(cookieStringBuilder, requestCookieMap); + + //合并响应头参数 + if (!org.springframework.util.CollectionUtils.isEmpty(responseHeaderMap)) { + responseHeadersMapFromPlugin.putAll(responseHeaderMap); + } + + } + //添加cookie至header + headersMapFromPlugin.put("Cookie", cookieStringBuilder.toString()); + //重构请求参数 + URI newUri = addQueryParam(exchange.getRequest().getURI(), queryParamFromPlugin); + builder.uri(newUri); + builder.headers(httpHeader -> httpHeader.setAll(headersMapFromPlugin)); + //添加响应头至请求上下文 + if (CollectionUtils.isNotEmpty(responseHeadersMapFromPlugin)) { + responseHeadersMapFromPlugin.forEach((k, v) -> exchange.getResponse().getHeaders().add(k, v)); + } + return exchange.mutate().request(builder.build()).build(); + } + + private List pluginCheck(ServerWebExchange exchange, GroupContext.ContextRoute contextRoute) { + + List pluginPayloadList = new ArrayList<>(); + + List pluginDetails = manager.getPluginDetails(config.getGroup(), contextRoute.getApiId()); + + if (pluginDetails == null || pluginDetails.size() == 0) { + if (logger.isDebugEnabled()) { + logger.debug("[doPlugins] No plugin found for group: {}, path:{}, path id: {}", config.getGroup(), contextRoute.getPath(), contextRoute.getApiId()); + } + return pluginPayloadList; + } + + TsfGatewayRequest tsfGatewayRequest = buildTsfGatewayRequest(exchange); + + //遍历绑定的插件 + for (PluginDetail pluginDetail : pluginDetails) { + PluginInfo pluginInfo = manager.getPluginInfo(pluginDetail.getId()); + //pluginInfo.check(); + //执行插件逻辑 + if (logger.isDebugEnabled()) { + logger.debug("pluginInfo is : {}", pluginInfo); + } + IGatewayPlugin gatewayPluginExecutor; + try { + gatewayPluginExecutor = GatewayPluginFactory.getGatewayPluginExecutor(pluginInfo.getType()); + + } + catch (Throwable t) { + //日志打印出原始异常 + logger.error("build pluginExecute error", t); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "build pluginExecute error"); + } + + if (gatewayPluginExecutor == null) { + logger.error("can not find pluginExecutor"); + return Collections.emptyList(); + } + + PluginPayload pluginPayload = gatewayPluginExecutor.invoke(pluginInfo, tsfGatewayRequest); + if (logger.isDebugEnabled()) { + logger.debug("plugin invoke result: {}", pluginPayload); + } + pluginPayloadList.add(pluginPayload); + } + return pluginPayloadList; + + } + + private ServerWebExchange externalFilter(ServerWebExchange exchange, GatewayFilterChain chain, PathContainer path) { ServerHttpRequest request = exchange.getRequest(); - String[] apis = rebuildExternalApi(request, request.getPath().value()); + String[] apis = rebuildExternalApi(request, path.value()); GroupContext.ContextRoute contextRoute = manager.getGroupPathRoute(config.getGroup(), apis[0]); if (contextRoute == null) { - String msg = String.format("[externalFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath()); + String msg = String.format("[externalFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], path.value()); logger.warn(msg); throw NotFoundException.create(true, msg); } updateRouteMetadata(exchange, contextRoute); + exchange.getAttributes().put(GatewayConstant.CONTEXT_ROUTE, contextRoute); URI requestUri = URI.create(contextRoute.getHost() + apis[1]); exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri); // to correct path ServerHttpRequest newRequest = request.mutate().path(apis[1]).build(); - return chain.filter(exchange.mutate().request(newRequest).build()); + return exchange.mutate().request(newRequest).build(); } - private Mono msFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext) { + private ServerWebExchange msFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext, PathContainer path) { ServerHttpRequest request = exchange.getRequest(); - String[] apis = rebuildMsApi(request, groupContext, request.getPath().value()); + String[] apis = rebuildMsApi(request, groupContext, path.value()); + logger.debug("[msFilter] path:{}, apis: {}", path, apis); // check api GroupContext.ContextRoute contextRoute = manager.getGroupPathRoute(config.getGroup(), apis[0]); if (contextRoute == null) { - String msg = String.format("[msFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath()); + String msg = String.format("[msFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], path.value()); logger.warn(msg); throw NotFoundException.create(true, msg); } updateRouteMetadata(exchange, contextRoute); + exchange.getAttributes().put(GatewayConstant.CONTEXT_ROUTE, contextRoute); MetadataContext metadataContext = exchange.getAttribute( MetadataConstant.HeaderName.METADATA_CONTEXT); if (metadataContext != null) { + // priority: namespace id(tsf) > namespace name(polaris) + String routeNamespace = StringUtils.isBlank(contextRoute.getNamespaceId()) ? + contextRoute.getNamespace() : contextRoute.getNamespaceId(); metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE, - MetadataConstant.POLARIS_TARGET_NAMESPACE, contextRoute.getNamespace()); + MetadataConstant.POLARIS_TARGET_NAMESPACE, routeNamespace); } URI requestUri = URI.create("lb://" + contextRoute.getService() + apis[1]); exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri); // to correct path ServerHttpRequest newRequest = request.mutate().path(apis[1]).build(); - return chain.filter(exchange.mutate().request(newRequest).build()); + return exchange.mutate().request(newRequest).build(); } /** @@ -141,28 +311,37 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered { Position namespacePosition = groupContext.getPredicate().getNamespace().getPosition(); switch (namespacePosition) { - case QUERY: - matchPath.append("/").append(request.getQueryParams().getFirst(groupContext.getPredicate().getNamespace().getKey())); - break; - case HEADER: - matchPath.append("/").append(request.getHeaders().getFirst(groupContext.getPredicate().getNamespace().getKey())); - break; - case PATH: - default: + case QUERY: + matchPath.append("/") + .append(request.getQueryParams().getFirst(groupContext.getPredicate().getNamespace().getKey())); + break; + case HEADER: + matchPath.append("/") + .append(request.getHeaders().getFirst(groupContext.getPredicate().getNamespace().getKey())); + break; + case PATH: + default: + if (index < pathSegments.length - 1) { matchPath.append("/").append(pathSegments[index++]); - break; + } + break; } Position servicePosition = groupContext.getPredicate().getService().getPosition(); switch (servicePosition) { - case QUERY: - matchPath.append("/").append(request.getQueryParams().getFirst(groupContext.getPredicate().getService().getKey())); - break; - case HEADER: - matchPath.append("/").append(request.getHeaders().getFirst(groupContext.getPredicate().getService().getKey())); - break; - case PATH: - default: + case QUERY: + matchPath.append("/") + .append(request.getQueryParams().getFirst(groupContext.getPredicate().getService().getKey())); + break; + case HEADER: + matchPath.append("/") + .append(request.getHeaders().getFirst(groupContext.getPredicate().getService().getKey())); + break; + case PATH: + default: + if (index < pathSegments.length - 1) { matchPath.append("/").append(pathSegments[index++]); + } + break; } StringBuilder realPath = new StringBuilder(); for (int i = index; i < pathSegments.length; i++) { @@ -202,4 +381,117 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered { // after RouteToRequestUrlFilter, DecodeTransferMetadataReactiveFilter return RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 12; } + + private TsfGatewayRequest buildTsfGatewayRequest(ServerWebExchange exchange) { + ServerHttpRequest request = exchange.getRequest(); + URI uri = request.getURI(); + TsfGatewayRequest gatewayRequest = new TsfGatewayRequest(); + gatewayRequest.setUri(uri); + gatewayRequest.setMethod(request.getMethodValue()); + gatewayRequest.setHeaders(buildTsfGatewayRequestHeader(exchange)); + gatewayRequest.setParameterMap(buildTsfGatewayRequestParameter(request.getQueryParams())); + gatewayRequest.setRequestHeaders(copyRequestHeader(request)); + gatewayRequest.setCookieMap(getCookieMap(request)); + + return gatewayRequest; + } + + private Map buildTsfGatewayRequestHeader(ServerWebExchange exchange) { + ServerHttpRequest servletRequest = exchange.getRequest(); + HttpHeaders headers = servletRequest.getHeaders(); + String traceId = exchange.getAttribute(HeaderName.TRACE_ID); + + if (StringUtils.isEmpty(traceId)) { + traceId = IdGenerator.generate(); + } + // 设置TraceId + exchange.getAttributes().put(HeaderName.TRACE_ID, traceId); + + Map headerMap = new LinkedHashMap<>(); + headerMap.put(HeaderName.NODE, headers.getFirst(HeaderName.NODE)); + headerMap.put(HeaderName.APP_KEY, headers.getFirst(HeaderName.APP_KEY)); + headerMap.put(HeaderName.ALG, headers.getFirst(HeaderName.ALG)); + headerMap.put(HeaderName.SIGN, headers.getFirst(HeaderName.SIGN)); + headerMap.put(HeaderName.TRACE_ID, traceId); + headerMap.put(HeaderName.NONCE, headers.getFirst(HeaderName.NONCE)); + String namespaceIdKey = HeaderName.NAMESPACE_ID; + headerMap.put(namespaceIdKey, headers.getFirst(namespaceIdKey)); + return headerMap; + } + + private Map buildTsfGatewayRequestParameter(MultiValueMap queryParams) { + Map map = new HashMap<>(); + if (queryParams != null) { + for (Map.Entry> entry : queryParams.entrySet()) { + List value = entry.getValue(); + String[] newValue = new String[value.size()]; + map.put(entry.getKey(), value.toArray(newValue)); + } + } + return map; + } + + private Map copyRequestHeader(ServerHttpRequest request) { + Map headerMap = new LinkedHashMap<>(); + HttpHeaders headers = request.getHeaders(); + for (Map.Entry> entry : headers.entrySet()) { + if (!entry.getValue().isEmpty()) { + headerMap.put(entry.getKey(), entry.getValue().get(0)); + } + } + return headerMap; + } + + private Map getCookieMap(ServerHttpRequest request) { + Map map = new LinkedHashMap<>(); + MultiValueMap cookie = request.getCookies(); + for (Map.Entry> entry : cookie.entrySet()) { + List httpCookies = entry.getValue(); + if (!httpCookies.isEmpty()) { + map.put(entry.getKey(), httpCookies.get(0).getValue()); + } + } + return map; + } + + private URI addQueryParam(URI uri, Map queryParam) { + + StringBuilder query = new StringBuilder(); + String originalQuery = uri.getRawQuery(); + + if (org.springframework.util.StringUtils.hasText(originalQuery)) { + query.append(originalQuery); + } + + for (Map.Entry entry : queryParam.entrySet()) { + String[] values = entry.getValue(); + for (String value : values) { + if (query.length() == 0 || query.charAt(query.length() - 1) != '&') { + query.append('&'); + } + + try { + String encodedValue = URLEncoder.encode(value, "UTF-8"); + query.append(entry.getKey()); + query.append('='); + query.append(encodedValue); + } + catch (UnsupportedEncodingException e) { + logger.warn("value {} cannot be encoded to UTF-8", value, e); + } + } + } + + try { + URI newUri = UriComponentsBuilder.fromUri(uri) + .replaceQuery(query.toString()) + .build(true) + .toUri(); + + return newUri; + } + catch (RuntimeException ex) { + throw new IllegalStateException("Invalid URI query: \"" + query + "\""); + } + } } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayProperties.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayProperties.java index 5dddad8a8..5f886dded 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayProperties.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayProperties.java @@ -17,9 +17,12 @@ package com.tencent.cloud.plugin.gateway.context; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import com.tencent.tsf.gateway.core.model.PluginInstanceInfo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -41,6 +44,10 @@ public class ContextGatewayProperties { private Map groups = new HashMap<>(); + private List pathRewrites = new ArrayList<>(); + + private List plugins; + public Map getRoutes() { return routes; } @@ -60,6 +67,22 @@ public class ContextGatewayProperties { this.groups = groups; } + public List getPathRewrites() { + return pathRewrites; + } + + public void setPathRewrites(List pathRewrites) { + this.pathRewrites = pathRewrites; + } + + public List getPlugins() { + return plugins; + } + + public void setPlugins(List plugins) { + this.plugins = plugins; + } + @Override public String toString() { return new ToStringCreator(this).append("routes", routes) diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java index 304a28e5a..38ecd8af6 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java @@ -17,9 +17,14 @@ package com.tencent.cloud.plugin.gateway.context; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.metadata.MetadataContext; @@ -27,8 +32,18 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient; import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient; import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.tsf.gateway.core.constant.PluginScopeType; +import com.tencent.tsf.gateway.core.constant.PluginType; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; +import com.tencent.tsf.gateway.core.model.PluginArgInfo; +import com.tencent.tsf.gateway.core.model.PluginDetail; +import com.tencent.tsf.gateway.core.model.PluginInfo; +import com.tencent.tsf.gateway.core.model.PluginInstanceInfo; +import com.tencent.tsf.gateway.core.util.PluginUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import shade.polaris.org.apache.commons.beanutils.BeanUtils; import org.springframework.util.AntPathMatcher; @@ -43,10 +58,26 @@ public class ContextGatewayPropertiesManager { * context -> {wildcard path key -> route}. */ private volatile ConcurrentHashMap> groupWildcardPathRouteMap = new ConcurrentHashMap<>(); + /** + * group -> plugin info. + */ + private volatile ConcurrentHashMap groupPluginInfoMap = new ConcurrentHashMap<>(); + /** + * api id -> plugin info. + */ + private volatile ConcurrentHashMap apiPluginInfoMap = new ConcurrentHashMap<>(); + /** + * plugin id -> plugin info. + */ + private volatile ConcurrentHashMap gatewayPluginInfoMap = new ConcurrentHashMap<>(); private Map groups = new HashMap<>(); - private AntPathMatcher antPathMatcher = new AntPathMatcher(); + private List pathRewrites = new ArrayList<>(); + + private List plugins = new ArrayList<>(); + + private final AntPathMatcher antPathMatcher = new AntPathMatcher(); public Map> getGroupPathRouteMap() { return groupPathRouteMap; @@ -56,22 +87,81 @@ public class ContextGatewayPropertiesManager { return groupWildcardPathRouteMap; } - public void setGroupRouteMap(Map groups) { + public List getPathRewrites() { + return pathRewrites; + } + + public void setPathRewrites(List pathRewrites) { + this.pathRewrites = pathRewrites; + } + + public List getPlugins() { + return plugins; + } + + public void setPlugins(List plugins) { + this.plugins = plugins; + } + + public void refreshPlugins(List plugins) { + if (plugins != null) { + this.plugins = plugins; + + ConcurrentHashMap groupPluginInfoHashMap = new ConcurrentHashMap<>(); + ConcurrentHashMap apiPluginInfoHashMap = new ConcurrentHashMap<>(); + ConcurrentHashMap gatewayPluginInfoHashMap = new ConcurrentHashMap<>(); + + for (PluginInstanceInfo pluginInstanceInfo : plugins) { + + if (PluginScopeType.GROUP.getScopeType().equalsIgnoreCase(pluginInstanceInfo.getScopeType())) { + groupPluginInfoHashMap.put(pluginInstanceInfo.getScopeValue(), pluginInstanceInfo); + } + else if (PluginScopeType.API.getScopeType().equalsIgnoreCase(pluginInstanceInfo.getScopeType())) { + apiPluginInfoHashMap.put(pluginInstanceInfo.getScopeValue(), pluginInstanceInfo); + } + + List gatewayPluginDetails = pluginInstanceInfo.getPluginDetails(); + for (PluginDetail gatewayPluginDetail : gatewayPluginDetails) { + if (!gatewayPluginInfoHashMap.containsKey(gatewayPluginDetail.getId())) { + PluginInfo gatewayPluginInfo = buildGatewayPluginInfo(gatewayPluginDetail); + if (gatewayPluginInfo != null) { + //初始化的时候就应该检查,避免运行时检查,在QPS高的时候,可以明显提升网关的性能 + gatewayPluginInfo.check(); + gatewayPluginInfoHashMap.put(gatewayPluginDetail.getId(), gatewayPluginInfo); + } + } + } + + } + + this.groupPluginInfoMap = groupPluginInfoHashMap; + this.apiPluginInfoMap = apiPluginInfoHashMap; + this.gatewayPluginInfoMap = gatewayPluginInfoHashMap; + } + } + + public void refreshGroupRoute(Map groups) { ConcurrentHashMap> newGroupPathRouteMap = new ConcurrentHashMap<>(); ConcurrentHashMap> newGroupWildcardPathRouteMap = new ConcurrentHashMap<>(); + if (groups != null) { for (Map.Entry entry : groups.entrySet()) { + GroupContext groupContext = entry.getValue(); Map newGroupPathRoute = new HashMap<>(); Map newGroupWildcardPathRoute = new HashMap<>(); - for (GroupContext.ContextRoute route : entry.getValue().getRoutes()) { + + for (GroupContext.ContextRoute route : groupContext.getRoutes()) { String path = route.getPath(); // convert path parameter to group wildcard path if (path.contains("{") && path.contains("}") || path.contains("*") || path.contains("?")) { - newGroupWildcardPathRoute.put(buildPathKey(entry.getValue(), route), route); + newGroupWildcardPathRoute.put(buildPathKey(groupContext, route), route); } else { - newGroupPathRoute.put(buildPathKey(entry.getValue(), route), route); + newGroupPathRoute.put(buildPathKey(groupContext, route), route); + if (StringUtils.isNotEmpty(route.getPathMapping())) { + newGroupPathRoute.put(buildPathMappingKey(groupContext, route), route); + } } } newGroupWildcardPathRouteMap.put(entry.getKey(), newGroupWildcardPathRoute); @@ -141,14 +231,88 @@ public class ContextGatewayPropertiesManager { } } - private String buildPathKey(GroupContext groupContext, GroupContext.ContextRoute route) { + private String buildPathKey(GroupContext groupContext, GroupContext.ContextRoute route) { switch (groupContext.getPredicate().getApiType()) { - case MS: - return String.format("%s|/%s/%s%s", route.getMethod(), route.getNamespace(), route.getService(), route.getPath()); - case EXTERNAL: - default: - return String.format("%s|%s", route.getMethod(), route.getPath()); + case MS: + return String.format("%s|/%s/%s%s", route.getMethod(), route.getNamespace(), route.getService(), route.getPath()); + case EXTERNAL: + default: + return String.format("%s|%s", route.getMethod(), route.getPath()); + } + } + + private String buildPathMappingKey(GroupContext groupContext, GroupContext.ContextRoute route) { + return String.format("%s|%s", route.getMethod(), route.getPathMapping()); + } + + protected PluginInfo buildGatewayPluginInfo(PluginDetail gatewayPluginDetail) { + //不同插件类型,作不同方式处理 + PluginInfo pluginInfo = deserializePlugin(gatewayPluginDetail, gatewayPluginDetail.getPluginArgInfos()); + + return pluginInfo; + } + + private T deserializePlugin(PluginInfo basePlugin, List pluginArgs) { + if (basePlugin == null || pluginArgs == null) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR); + } + + //查询网关插件参数信息,存储到map + Map pluginArgMap = new HashMap<>(pluginArgs.size()); + pluginArgs.forEach((pluginArg -> pluginArgMap.put(pluginArg.getKey(), pluginArg.getValue()))); + //不同插件类型,作不同方式处理 + T pluginDetail; + PluginType pluginType = PluginType.getPluginType(basePlugin.getType()); + if (pluginType == null) { + return null; + } + try { + //构建插件实例 + Class gatewayPluginClazz = pluginType.getPluginClazz(); + pluginDetail = (T) gatewayPluginClazz.newInstance(); + //复制插件通用属性 + org.springframework.beans.BeanUtils.copyProperties(basePlugin, pluginDetail); + //复制插件私有参数,map转为bean + BeanUtils.populate(pluginDetail, pluginArgMap); + } + catch (Throwable t) { + //日志打印出原始异常 + logger.error("build plugin error: plugin type is: {}", basePlugin.getType(), t); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "构建" + basePlugin.getType() + "插件异常"); } + + return pluginDetail; + } + + public List getPluginDetails(String group, String apiId) { + List pluginDetails = null; + + PluginInstanceInfo groupPluginInfo = groupPluginInfoMap.get(group); + PluginInstanceInfo apiPluginInfo = apiPluginInfoMap.get(Optional.ofNullable(apiId).orElse("")); + + Stream pluginDetailStream = null; + if (groupPluginInfo != null && apiPluginInfo != null) { + pluginDetailStream = Stream.of(groupPluginInfo.getPluginDetails(), apiPluginInfo.getPluginDetails()) + .flatMap(Collection::stream); + } + else if (groupPluginInfo != null) { + pluginDetailStream = groupPluginInfo.getPluginDetails().stream(); + } + else if (apiPluginInfo != null) { + pluginDetailStream = apiPluginInfo.getPluginDetails().stream(); + } + + // 去重、排序 + if (pluginDetailStream != null) { + pluginDetails = PluginUtil.sortPluginDetail(pluginDetailStream); + } + + return pluginDetails; + } + + public PluginInfo getPluginInfo(String pluginId) { + + return gatewayPluginInfoMap.get(Optional.ofNullable(pluginId).orElse("")); } } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextRoutePredicateFactory.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextRoutePredicateFactory.java index c04f9bcbb..6b7a8d31c 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextRoutePredicateFactory.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextRoutePredicateFactory.java @@ -23,12 +23,18 @@ import java.util.function.Predicate; import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate; +import org.springframework.http.server.PathContainer; import org.springframework.web.server.ServerWebExchange; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR; + public class ContextRoutePredicateFactory extends AbstractRoutePredicateFactory { - public ContextRoutePredicateFactory() { + private final ContextGatewayPropertiesManager manager; + + public ContextRoutePredicateFactory(ContextGatewayPropertiesManager manager) { super(Config.class); + this.manager = manager; } @Override @@ -36,7 +42,14 @@ public class ContextRoutePredicateFactory extends AbstractRoutePredicateFactory< return new GatewayPredicate() { @Override public boolean test(ServerWebExchange exchange) { - // TODO: do path-rewriting , put to GATEWAY_PREDICATE_PATH_CONTAINER_ATTR + if (exchange.getAttributes().containsKey(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR)) { + return true; + } + // do path-rewriting + String path = exchange.getRequest().getURI().getRawPath(); + String rewritePath = PathRewriteUtil.getNewPath(manager.getPathRewrites(), path, exchange); + exchange.getAttributes() + .put(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR, PathContainer.parsePath(rewritePath)); return true; } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GatewayConfigChangeListener.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GatewayConfigChangeListener.java index 1a2637d78..088b38b42 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GatewayConfigChangeListener.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GatewayConfigChangeListener.java @@ -29,14 +29,14 @@ import org.springframework.core.env.Environment; public class GatewayConfigChangeListener { - private ApplicationEventPublisher publisher; + private final ApplicationEventPublisher publisher; - private ContextGatewayPropertiesManager manager; + private final ContextGatewayPropertiesManager manager; - private Environment environment; + private final Environment environment; public GatewayConfigChangeListener(ContextGatewayPropertiesManager manager, - ApplicationEventPublisher publisher, Environment environment) { + ApplicationEventPublisher publisher, Environment environment) { this.manager = manager; this.publisher = publisher; this.environment = environment; @@ -47,7 +47,10 @@ public class GatewayConfigChangeListener { Binder binder = Binder.get(environment); BindResult result = binder.bind(ContextGatewayProperties.PREFIX, ContextGatewayProperties.class); if (result.isBound()) { - manager.setGroupRouteMap(result.get().getGroups()); + manager.refreshGroupRoute(result.get().getGroups()); + manager.refreshPlugins(result.get().getPlugins()); + manager.setPathRewrites(result.get().getPathRewrites()); + this.publisher.publishEvent(new RefreshRoutesEvent(event)); } } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GatewayConsulRepo.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GatewayConsulRepo.java new file mode 100644 index 000000000..4e4da3cef --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GatewayConsulRepo.java @@ -0,0 +1,474 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import com.ecwid.consul.v1.ConsulClient; +import com.ecwid.consul.v1.ConsulRawClient; +import com.ecwid.consul.v1.QueryParams; +import com.ecwid.consul.v1.Response; +import com.ecwid.consul.v1.kv.model.GetValue; +import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.polaris.context.PolarisSDKContextManager; +import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.polaris.client.util.NamedThreadFactory; +import com.tencent.polaris.factory.config.global.ServerConnectorConfigImpl; +import com.tencent.polaris.plugins.configuration.connector.consul.ConsulConfigConstants; +import com.tencent.polaris.plugins.configuration.connector.consul.ConsulConfigContext; +import com.tencent.tsf.gateway.core.constant.AuthMode; +import com.tencent.tsf.gateway.core.constant.GatewayConstant; +import com.tencent.tsf.gateway.core.model.GatewayAllResult; +import com.tencent.tsf.gateway.core.model.Group; +import com.tencent.tsf.gateway.core.model.GroupApi; +import com.tencent.tsf.gateway.core.model.GroupApiResult; +import com.tencent.tsf.gateway.core.model.GroupResult; +import com.tencent.tsf.gateway.core.model.GroupSecret; +import com.tencent.tsf.gateway.core.model.PathRewriteResult; +import com.tencent.tsf.gateway.core.model.PathWildcardResult; +import com.tencent.tsf.gateway.core.model.PathWildcardRule; +import com.tencent.tsf.gateway.core.model.PluginInstanceInfoResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.gateway.event.RefreshRoutesEvent; +import org.springframework.cloud.gateway.filter.FilterDefinition; +import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; +import org.springframework.cloud.gateway.route.RouteDefinition; +import org.springframework.context.ApplicationEventPublisher; + +import static com.tencent.polaris.api.config.plugin.DefaultPlugins.SERVER_CONNECTOR_CONSUL; + +public class GatewayConsulRepo { + + private static final Logger logger = LoggerFactory.getLogger(GatewayConsulRepo.class); + + private final ContextGatewayProperties contextGatewayProperties; + + private final ContextGatewayPropertiesManager contextGatewayPropertiesManager; + + private final ApplicationEventPublisher publisher; + private final AtomicLong gatewayGroupIndex = new AtomicLong(-1); + private final AtomicLong commonPluginIndex = new AtomicLong(-1); + private ConsulClient consulClient; + private ConsulConfigContext consulConfigContext; + private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2, new NamedThreadFactory("consul-gateway-watch", true)); + + public GatewayConsulRepo(ContextGatewayProperties contextGatewayProperties, + PolarisSDKContextManager polarisSDKContextManager, + ContextGatewayPropertiesManager contextGatewayPropertiesManager, + ApplicationEventPublisher publisher, String tsfGroupId) { + this.contextGatewayProperties = contextGatewayProperties; + this.contextGatewayPropertiesManager = contextGatewayPropertiesManager; + this.publisher = publisher; + + List serverConnectorConfigs = polarisSDKContextManager.getSDKContext().getConfig() + .getGlobal().getServerConnectors(); + if (serverConnectorConfigs == null || serverConnectorConfigs.size() != 1 || !SERVER_CONNECTOR_CONSUL.equals(serverConnectorConfigs.get(0) + .getProtocol())) { + logger.warn("GatewayConsulRepo not enable, serverConnectorConfigs:{}", serverConnectorConfigs); + return; + } + + // init consul client + ServerConnectorConfigImpl connectorConfig = serverConnectorConfigs.get(0); + + if (CollectionUtils.isEmpty(connectorConfig.getAddresses())) { + logger.warn("GatewayConsulRepo not enable, connectorConfig:{}", connectorConfig); + return; + } + + String address = connectorConfig.getAddresses().get(0); + int lastIndex = address.lastIndexOf(":"); + String agentHost = address.substring(0, lastIndex); + int agentPort = Integer.parseInt(address.substring(lastIndex + 1)); + logger.info("Connect to consul config server : [{}].", address); + + consulClient = new ConsulClient(new ConsulRawClient(agentHost, agentPort)); + initConsulConfigContext(connectorConfig); + + Response> listResponse = consulClient.getKVValues("tsf_gateway/" + tsfGroupId, consulConfigContext.getAclToken()); + + gatewayGroupIndex.set(listResponse.getConsulIndex()); + if (listResponse.getValue() != null) { + refreshGatewayGroupConfig(parseGroupResponse(listResponse)); + } + + scheduledExecutorService.scheduleAtFixedRate(() -> { + try { + Response> watchResponse = consulClient.getKVValues("tsf_gateway/" + tsfGroupId, + consulConfigContext.getAclToken(), new QueryParams(consulConfigContext.getWaitTime(), gatewayGroupIndex.get())); + // 404 + if (watchResponse.getValue() == null) { + return; + } + // 200 + Long newIndex = watchResponse.getConsulIndex(); + if (logger.isDebugEnabled()) { + logger.debug("[watch group] index: {}, newIndex: {}", gatewayGroupIndex.get(), newIndex); + } + if (newIndex != null && !Objects.equals(gatewayGroupIndex.get(), newIndex)) { + gatewayGroupIndex.set(newIndex); + refreshGatewayGroupConfig(parseGroupResponse(watchResponse)); + this.publisher.publishEvent(new RefreshRoutesEvent(this)); + } + + } + catch (Exception e) { + logger.warn("Gateway config watch error.", e); + try { + Thread.sleep(consulConfigContext.getConsulErrorSleep()); + } + catch (Exception ex) { + logger.error("error in sleep, msg: " + e.getMessage()); + } + } + + }, consulConfigContext.getDelay(), consulConfigContext.getDelay(), TimeUnit.MILLISECONDS); + + + scheduledExecutorService.scheduleAtFixedRate(() -> { + try { + Response> watchResponse = consulClient.getKVValues("tsf_gateway/common/plugin", + consulConfigContext.getAclToken(), new QueryParams(consulConfigContext.getWaitTime(), commonPluginIndex.get())); + // 404 + if (watchResponse.getValue() == null) { + return; + } + // 200 + Long newIndex = watchResponse.getConsulIndex(); + if (logger.isDebugEnabled()) { + logger.debug("[watch plugin] index: {}, newIndex: {}", commonPluginIndex.get(), newIndex); + } + if (newIndex != null && !Objects.equals(commonPluginIndex.get(), newIndex)) { + commonPluginIndex.set(listResponse.getConsulIndex()); + parsePluginResponse(watchResponse); + } + } + catch (Exception e) { + logger.error("Gateway plugin watch error.", e); + try { + Thread.sleep(consulConfigContext.getConsulErrorSleep()); + } + catch (Exception ex) { + logger.error("error in sleep, msg: " + e.getMessage()); + } + } + }, consulConfigContext.getDelay(), consulConfigContext.getDelay(), TimeUnit.MILLISECONDS); + + } + + private void initConsulConfigContext(ServerConnectorConfigImpl connectorConfig) { + // init consul config context. + consulConfigContext = new ConsulConfigContext(); + // token + String tokenStr = connectorConfig.getToken(); + if (StringUtils.isNotBlank(tokenStr)) { + consulConfigContext.setAclToken(tokenStr); + } + + Map metadata = connectorConfig.getMetadata(); + if (CollectionUtils.isNotEmpty(metadata)) { + String waitTimeStr = metadata.get(ConsulConfigConstants.WAIT_TIME_KEY); + if (StringUtils.isNotBlank(waitTimeStr)) { + try { + int waitTime = Integer.parseInt(waitTimeStr); + consulConfigContext.setWaitTime(waitTime); + } + catch (Exception e) { + logger.warn("wait time string {} is not integer.", waitTimeStr, e); + } + } + + String delayStr = metadata.get(ConsulConfigConstants.DELAY_KEY); + if (StringUtils.isNotBlank(delayStr)) { + try { + int delay = Integer.parseInt(delayStr); + consulConfigContext.setDelay(delay); + } + catch (Exception e) { + logger.warn("delay string {} is not integer.", delayStr, e); + } + } + + String consulErrorSleepStr = metadata.get(ConsulConfigConstants.CONSUL_ERROR_SLEEP_KEY); + if (StringUtils.isNotBlank(consulErrorSleepStr)) { + try { + long consulErrorSleep = Long.parseLong(consulErrorSleepStr); + consulConfigContext.setConsulErrorSleep(consulErrorSleep); + } + catch (Exception e) { + logger.warn("delay string {} is not integer.", consulErrorSleepStr, e); + } + } + } + } + + private void parsePluginResponse(Response> listResponse) { + + PluginInstanceInfoResult pluginInstanceInfoResult = new PluginInstanceInfoResult(); + pluginInstanceInfoResult.setResult(new ArrayList<>()); + + for (GetValue getValue : listResponse.getValue()) { + if (logger.isDebugEnabled()) { + logger.debug("[parseResponse] Received plugin data: {}", getValue.getDecodedValue()); + } + PluginInstanceInfoResult temp = JacksonUtils.deserialize(getValue.getDecodedValue(), PluginInstanceInfoResult.class); + pluginInstanceInfoResult.getResult().addAll(temp.getResult()); + } + + saveAsFile(JacksonUtils.serialize2Json(pluginInstanceInfoResult), GatewayConstant.PLUGIN_FILE_NAME); + + contextGatewayProperties.setPlugins(pluginInstanceInfoResult.getResult()); + contextGatewayPropertiesManager.refreshPlugins(contextGatewayProperties.getPlugins()); + + } + + private GatewayAllResult parseGroupResponse(Response> listResponse) { + GroupResult groupResult = null; + GroupApiResult groupApiResult = new GroupApiResult(); + groupApiResult.setResult(new ArrayList<>()); + + PathRewriteResult pathRewriteResult = new PathRewriteResult(); + PathWildcardResult pathWildcardResult = null; + + + for (GetValue getValue : listResponse.getValue()) { + String key = getValue.getKey(); + String[] keySplit = key.split("/"); + // format example: tsf_gateway/group-xxx/group/data + if (keySplit.length < 4) { + continue; + } + switch (keySplit[2]) { + case GatewayConstant.GROUP_FILE_NAME: + if (logger.isDebugEnabled()) { + logger.debug("[parseResponse] Received group data: {}", getValue.getDecodedValue()); + } + groupResult = JacksonUtils.deserialize(getValue.getDecodedValue(), GroupResult.class); + break; + case GatewayConstant.API_FILE_NAME: + if (logger.isDebugEnabled()) { + logger.debug("[parseResponse] Received api data: {}", getValue.getDecodedValue()); + } + GroupApiResult temp = JacksonUtils.deserialize(getValue.getDecodedValue(), GroupApiResult.class); + groupApiResult.getResult().addAll(temp.getResult()); + break; + case GatewayConstant.PATH_REWRITE_FILE_NAME: + if (logger.isDebugEnabled()) { + logger.debug("[parseResponse] Received path rewrite data: {}", getValue.getDecodedValue()); + } + pathRewriteResult = JacksonUtils.deserialize(getValue.getDecodedValue(), PathRewriteResult.class); + break; + case GatewayConstant.PATH_WILDCARD_FILE_NAME: + if (logger.isDebugEnabled()) { + logger.debug("[parseResponse] Received path wildcard data: {}", getValue.getDecodedValue()); + } + pathWildcardResult = JacksonUtils.deserialize(getValue.getDecodedValue(), PathWildcardResult.class); + break; + } + + } + + saveAsFile(JacksonUtils.serialize2Json(groupResult), GatewayConstant.GROUP_FILE_NAME); + saveAsFile(JacksonUtils.serialize2Json(groupApiResult), GatewayConstant.API_FILE_NAME); + saveAsFile(JacksonUtils.serialize2Json(pathRewriteResult), GatewayConstant.PATH_REWRITE_FILE_NAME); + saveAsFile(JacksonUtils.serialize2Json(pathWildcardResult), GatewayConstant.PATH_WILDCARD_FILE_NAME); + + return new GatewayAllResult(groupResult, groupApiResult, pathRewriteResult, pathWildcardResult); + } + + private void refreshGatewayGroupConfig(GatewayAllResult gatewayAllResult) { + GroupResult groupResult = gatewayAllResult.getGroupResult(); + GroupApiResult groupApiResult = gatewayAllResult.getGroupApiResult(); + PathRewriteResult pathRewriteResult = gatewayAllResult.getPathRewriteResult(); + PathWildcardResult pathWildcardResult = gatewayAllResult.getPathWildcardResult(); + + Map routes = new HashMap<>(); + Map groups = new HashMap<>(); + + if (groupResult != null && groupResult.getResult() != null) { + for (Group group : groupResult.getResult()) { + routes.put(group.getGroupId(), getRouteDefinition(group)); + + GroupContext.ContextPredicate contextPredicate = new GroupContext.ContextPredicate(); + contextPredicate.setApiType(ApiType.valueOf(group.getGroupType().toUpperCase(Locale.ROOT))); + contextPredicate.setContext(group.getGroupContext()); + contextPredicate.setNamespace(new GroupContext.ContextNamespace( + Position.valueOf(group.getNamespaceNameKeyPosition() + .toUpperCase(Locale.ROOT)), group.getNamespaceNameKey())); + contextPredicate.setService(new GroupContext.ContextService( + Position.valueOf(group.getServiceNameKeyPosition() + .toUpperCase(Locale.ROOT)), group.getServiceNameKey())); + + List secrets = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(group.getSecretList())) { + for (GroupSecret groupSecret : group.getSecretList()) { + GroupContext.ContextSecret contextSecret = new GroupContext.ContextSecret(); + contextSecret.setName(groupSecret.getSecretName()); + contextSecret.setId(groupSecret.getSecretId()); + contextSecret.setKey(groupSecret.getSecretKey()); + contextSecret.setStatus(groupSecret.getStatus()); + contextSecret.setExpiredTime(groupSecret.getExpiredTime()); + + secrets.add(contextSecret); + } + } + + GroupContext.ContextAuth auth = new GroupContext.ContextAuth(); + auth.setType(AuthMode.getMode(group.getAuthMode())); + auth.setSecrets(secrets); + + + GroupContext groupContext = new GroupContext(); + groupContext.setRoutes(new ArrayList<>()); + groupContext.setPredicate(contextPredicate); + groupContext.setAuth(auth); + + groups.put(group.getGroupId(), groupContext); + } + } + + for (GroupApi groupApi : groupApiResult.getResult()) { + GroupContext groupContext = groups.get(groupApi.getGroupId()); + if (groupContext == null) { + if (logger.isDebugEnabled()) { + logger.debug("group api {} not found in group {}", groupApi.getApiId(), groupApi.getGroupId()); + } + continue; + } + + GroupContext.ContextRoute contextRoute = new GroupContext.ContextRoute(); + contextRoute.setApiId(groupApi.getApiId()); + contextRoute.setHost(groupApi.getHost()); + contextRoute.setPath(groupApi.getPath()); + contextRoute.setPathMapping(groupApi.getPathMapping()); + contextRoute.setMethod(groupApi.getMethod()); + contextRoute.setService(groupApi.getServiceName()); + contextRoute.setNamespaceId(groupApi.getNamespaceId()); + contextRoute.setNamespace(groupApi.getNamespaceName()); + if (groupApi.getTimeout() != null) { + Map metadata = new HashMap<>(); + metadata.put("response-timeout", String.valueOf(groupApi.getTimeout())); + contextRoute.setMetadata(metadata); + } + groupContext.getRoutes().add(contextRoute); + } + + if (pathWildcardResult != null && pathWildcardResult.getResult() != null) { + for (PathWildcardRule wildcardRule : pathWildcardResult.getResult()) { + + GroupContext.ContextRoute contextRoute = new GroupContext.ContextRoute(); + contextRoute.setPath(wildcardRule.getWildCardPath()); + contextRoute.setMethod(wildcardRule.getMethod()); + contextRoute.setService(wildcardRule.getServiceName()); + contextRoute.setNamespaceId(wildcardRule.getNamespaceId()); + contextRoute.setNamespace(wildcardRule.getNamespaceName()); + if (wildcardRule.getTimeout() != null) { + Map metadata = new HashMap<>(); + metadata.put("response-timeout", String.valueOf(wildcardRule.getTimeout())); + contextRoute.setMetadata(metadata); + } + + GroupContext groupContext = groups.get(wildcardRule.getGroupId()); + groupContext.getRoutes().add(contextRoute); + } + } + + contextGatewayProperties.setGroups(groups); + contextGatewayProperties.setRoutes(routes); + contextGatewayProperties.setPathRewrites(Optional.ofNullable(pathRewriteResult.getResult()) + .orElse(new ArrayList<>())); + + logger.debug("Gateway config loaded. :{}", JacksonUtils.serialize2Json(contextGatewayProperties)); + + contextGatewayPropertiesManager.setPathRewrites(contextGatewayProperties.getPathRewrites()); + + + contextGatewayPropertiesManager.refreshGroupRoute(contextGatewayProperties.getGroups()); + } + + private void saveAsFile(String data, String type) { + try { + // 写入文件 + OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(getRepoStoreFile(type))); + writer.write(data); + writer.close(); + } + catch (Throwable t) { + logger.warn("[tsf-gateway] save as file occur exception.", t); + } + } + + private File getRepoStoreFile(String type) { + String filePath = GatewayConstant.GATEWAY_REPO_PREFIX + type + GatewayConstant.FILE_SUFFIX; + File file = new File(filePath); + try { + if (!file.exists()) { + file.getParentFile().mkdirs(); + file.createNewFile(); + } + } + catch (IOException e) { + + logger.warn("[tsf-gateway] load group info from local file occur error. filePath: " + filePath, e); + } + return file; + } + + private RouteDefinition getRouteDefinition(Group group) { + RouteDefinition routeDefinition = new RouteDefinition(); + routeDefinition.setUri(URI.create("lb://" + group.getGroupId())); + + PredicateDefinition contextPredicateDefinition = new PredicateDefinition(); + contextPredicateDefinition.setName("Context"); + contextPredicateDefinition.setArgs(Collections.singletonMap("group", group.getGroupId())); + PredicateDefinition pathPredicateDefinition = new PredicateDefinition(); + pathPredicateDefinition.setName("Path"); + pathPredicateDefinition.setArgs(Collections.singletonMap("pattern", group.getGroupContext() + "/**")); + routeDefinition.setPredicates(Arrays.asList(contextPredicateDefinition, pathPredicateDefinition)); + + FilterDefinition contextFilterDefinition = new FilterDefinition(); + contextFilterDefinition.setName("Context"); + contextFilterDefinition.setArgs(Collections.singletonMap("group", group.getGroupId())); + routeDefinition.setFilters(Collections.singletonList(contextFilterDefinition)); + + routeDefinition.setOrder(-1); + + return routeDefinition; + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GroupContext.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GroupContext.java index a1817b4e8..bcd0ce136 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GroupContext.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GroupContext.java @@ -20,6 +20,8 @@ package com.tencent.cloud.plugin.gateway.context; import java.util.List; import java.util.Map; +import com.tencent.tsf.gateway.core.constant.AuthMode; + public class GroupContext { private String comment; @@ -28,6 +30,8 @@ public class GroupContext { private List routes; + private ContextAuth auth; + public String getComment() { return comment; } @@ -52,6 +56,14 @@ public class GroupContext { this.routes = routes; } + public ContextAuth getAuth() { + return auth; + } + + public void setAuth(ContextAuth auth) { + this.auth = auth; + } + public static class ContextPredicate { private ApiType apiType; @@ -100,6 +112,15 @@ public class GroupContext { private String key; + public ContextNamespace() { + + } + + public ContextNamespace(Position position, String key) { + this.position = position; + this.key = key; + } + public Position getPosition() { return position; } @@ -122,6 +143,15 @@ public class GroupContext { private String key; + public ContextService() { + + } + + public ContextService(Position position, String key) { + this.position = position; + this.key = key; + } + public Position getPosition() { return position; } @@ -146,12 +176,16 @@ public class GroupContext { private String method; + private String apiId; + private String service; private String host; private String namespace; + private String namespaceId; + private Map metadata; public String getPath() { @@ -178,6 +212,14 @@ public class GroupContext { this.method = method; } + public String getApiId() { + return apiId; + } + + public void setApiId(String apiId) { + this.apiId = apiId; + } + public String getService() { return service; } @@ -202,6 +244,14 @@ public class GroupContext { this.namespace = namespace; } + public String getNamespaceId() { + return namespaceId; + } + + public void setNamespaceId(String namespaceId) { + this.namespaceId = namespaceId; + } + public Map getMetadata() { return metadata; } @@ -210,4 +260,79 @@ public class GroupContext { this.metadata = metadata; } } + + public static class ContextAuth { + private AuthMode type; + + private List secrets; + + public AuthMode getType() { + return type; + } + + public void setType(AuthMode type) { + this.type = type; + } + + public List getSecrets() { + return secrets; + } + + public void setSecrets(List secrets) { + this.secrets = secrets; + } + } + + public static class ContextSecret { + private String name; + + private String id; + + private String key; + + private String status; + + private String expiredTime; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getExpiredTime() { + return expiredTime; + } + + public void setExpiredTime(String expiredTime) { + this.expiredTime = expiredTime; + } + } + } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/PathRewrite.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/PathRewrite.java new file mode 100644 index 000000000..d0e6798fd --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/PathRewrite.java @@ -0,0 +1,133 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * @author seanlxliu + * @date 2020/5/14 + */ +public class PathRewrite { + + /** + * 路径重写规则ID. + */ + private String pathRewriteId; + + /** + * 网关部署组ID. + */ + private String gatewayGroupId; + + /** + * 正则表达式. + */ + private String regex; + + /** + * 替换的内容. + */ + private String replacement; + + /** + * 是否屏蔽映射后路径,Y: 是 N: 否. + */ + private String blocked; + + /** + * 规则顺序,越小优先级越高. + */ + private Integer order; + + /** + * 路径重写规则IDs. + */ + @JsonIgnore + private List pathRewriteIds; + + public String getPathRewriteId() { + return pathRewriteId; + } + + public void setPathRewriteId(String pathRewriteId) { + this.pathRewriteId = pathRewriteId; + } + + public String getGatewayGroupId() { + return gatewayGroupId; + } + + public void setGatewayGroupId(String gatewayGroupId) { + this.gatewayGroupId = gatewayGroupId; + } + + public String getRegex() { + return regex; + } + + public void setRegex(String regex) { + this.regex = regex; + } + + public String getReplacement() { + return replacement; + } + + public void setReplacement(String replacement) { + this.replacement = replacement; + } + + public String getBlocked() { + return blocked; + } + + public void setBlocked(String blocked) { + this.blocked = blocked; + } + + public Integer getOrder() { + return order; + } + + public void setOrder(Integer order) { + this.order = order; + } + + public List getPathRewriteIds() { + return pathRewriteIds; + } + + public void setPathRewriteIds(List pathRewriteIds) { + this.pathRewriteIds = pathRewriteIds; + } + + @Override + public String toString() { + return "PathRewrite{" + + "pathRewriteId='" + pathRewriteId + '\'' + + ", gatewayGroupId='" + gatewayGroupId + '\'' + + ", regex='" + regex + '\'' + + ", replacement='" + replacement + '\'' + + ", blocked='" + blocked + '\'' + + ", order=" + order + + '}'; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/PathRewriteUtil.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/PathRewriteUtil.java new file mode 100644 index 000000000..6bc61a9b8 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/PathRewriteUtil.java @@ -0,0 +1,65 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import java.util.List; + +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.tsf.gateway.core.constant.GatewayConstant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.server.ServerWebExchange; + + +public final class PathRewriteUtil { + + private static final Logger logger = LoggerFactory.getLogger(PathRewriteUtil.class); + private static final String BLOCKED = "Y"; + + private PathRewriteUtil() { + + } + + public static String getNewPath(List pathRewrites, String path, ServerWebExchange exchange) { + String newPath = path; + for (PathRewrite pathRewrite : pathRewrites) { + String regex = pathRewrite.getRegex(); + String replacement = pathRewrite.getReplacement(); + String blockedRegex = replacement.replaceAll("\\$(\\d+|\\{[^/]+?\\})", ".*"); + + if (!StringUtils.isBlank(blockedRegex) && BLOCKED.equalsIgnoreCase(pathRewrite.getBlocked()) + && path.matches(blockedRegex)) { + logger.warn("[TSF-MSGW] uri is blocked by rule. uri: [{}] rule: [{}] -> [{}]", + path, regex, replacement); + ResponseStatusException exception = new ResponseStatusException(HttpStatus.BAD_REQUEST, "uri is blocked by rule. uri: [" + path + "] rule: [" + regex + "] -> [" + replacement + "]"); + exchange.getAttributes().put(GatewayConstant.PATH_REWRITE_EXCEPTION, exception); + break; + } + + if (!StringUtils.isBlank(regex) && !StringUtils.isBlank(replacement) && path.matches(regex)) { + newPath = path.replaceAll(regex, replacement); + logger.info("[TSF-MSGW] rewrite path [{}] to [{}], rule: [{}] -> [{}]", path, newPath, regex, replacement); + break; + } + } + return newPath; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/Position.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/Position.java index b08081c3c..25385bd98 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/Position.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/Position.java @@ -17,6 +17,8 @@ package com.tencent.cloud.plugin.gateway.context; +import com.fasterxml.jackson.annotation.JsonCreator; + public enum Position { /** * Path position. @@ -30,4 +32,24 @@ public enum Position { * Header position. */ HEADER, + + /** + * HTTP COOKIE. + */ + COOKIE, + + /** + * TSF TAG,目前用于 Request Transformer Plugin 的改写流量. + */ + TSF_TAG; + + @JsonCreator + public static Position fromString(String key) { + for (Position position : Position.values()) { + if (position.name().equalsIgnoreCase(key)) { + return position; + } + } + return null; + } } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/TsfGatewayRequest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/TsfGatewayRequest.java new file mode 100644 index 000000000..7af56fbe7 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/TsfGatewayRequest.java @@ -0,0 +1,128 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core; + +import java.net.URI; +import java.util.Locale; +import java.util.Map; + +import com.tencent.polaris.api.utils.StringUtils; + +/** + * @author kysonli + * 2019/4/15 15:01 + */ +public class TsfGatewayRequest { + + private URI uri; + + private String method; + + private Map headers; + + /** + * 原始请求头,用于插件读取请求头信息. + */ + private Map requestHeaders; + + private Map parameterMap; + + private Map cookieMap; + + public URI getUri() { + return uri; + } + + public void setUri(URI uri) { + this.uri = uri; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + + public String getHeader(String headerKey) { + return headers.get(headerKey); + } + + public Map getParameterMap() { + return parameterMap; + } + + public void setParameterMap(Map parameterMap) { + this.parameterMap = parameterMap; + } + + public Map getRequestHeaders() { + return requestHeaders; + } + + public void setRequestHeaders(Map requestHeaders) { + this.requestHeaders = requestHeaders; + } + + public String getRequestHeader(String headerKey) { + return StringUtils.isNotEmpty(requestHeaders.get(headerKey)) ? requestHeaders.get(headerKey) : + requestHeaders.get(headerKey.toLowerCase(Locale.ROOT)); + } + + public void putRequestHeader(String headerKey, String headerValue) { + requestHeaders.put(headerKey.toLowerCase(Locale.ROOT), headerValue); + } + + public Map getCookieMap() { + return cookieMap; + } + + public void setCookieMap(Map cookieMap) { + this.cookieMap = cookieMap; + } + + public String getCookie(String key) { + return cookieMap.get(key); + } + + public void putCookie(String key, String value) { + cookieMap.put(key, value); + } + + @Override + public String toString() { + return "TsfGatewayRequest{" + + "uri=" + uri + + ", method='" + method + '\'' + + ", headers=" + headers + + ", requestHeaders=" + requestHeaders + + ", parameterMap=" + parameterMap + + ", cookieMap=" + cookieMap + + '}'; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/annotation/TsfGatewayFilter.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/annotation/TsfGatewayFilter.java new file mode 100644 index 000000000..1ca7fe258 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/annotation/TsfGatewayFilter.java @@ -0,0 +1,38 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.stereotype.Component; + +/** + * Compatible with old versions TSF SDK. + * + * @author Shedfree Wu + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface TsfGatewayFilter { +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/AuthMode.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/AuthMode.java new file mode 100644 index 000000000..39e43121e --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/AuthMode.java @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.constant; + +/** + * @author kysonli + * 2019/4/11 23:37 + */ +public enum AuthMode { + /** + * NONE. + */ + NONE("none"), + /** + * SECRET. + */ + SECRET("secret"); + + private final String authMode; + + AuthMode(String authMode) { + this.authMode = authMode; + } + + public static AuthMode getMode(String authMode) { + for (AuthMode groupAuthType : AuthMode.values()) { + if (groupAuthType.authMode.equalsIgnoreCase(authMode)) { + return groupAuthType; + } + } + return null; + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/CommonStatus.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/CommonStatus.java new file mode 100644 index 000000000..480278820 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/CommonStatus.java @@ -0,0 +1,61 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.constant; + +/** + * @author kysonli + * 2019/4/11 17:05 + */ +public enum CommonStatus { + /** + * ENABLED. + */ + ENABLED("enabled"), + /** + * DISABLED. + */ + DISABLED("disabled"); + + private final String status; + + CommonStatus(String status) { + this.status = status; + } + + public static CommonStatus getStatus(String status) { + for (CommonStatus commonStatus : CommonStatus.values()) { + if (commonStatus.status.equals(status)) { + return commonStatus; + } + } + return null; + } + + public static CommonStatus getStatus(String status, String errorMsg) { + for (CommonStatus commonStatus : CommonStatus.values()) { + if (commonStatus.status.equals(status)) { + return commonStatus; + } + } + return null; + } + + public String getStatus() { + return status; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/GatewayConstant.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/GatewayConstant.java new file mode 100644 index 000000000..d22e7f633 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/GatewayConstant.java @@ -0,0 +1,82 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.constant; + +/** + * @author kysonli + * 2019/4/10 15:34 + */ +public final class GatewayConstant { + /** + * CONTEXT_ROUTE. + */ + public static final String CONTEXT_ROUTE = "contextRoute"; + /** + * 网关能够访问的API分组的缓存文件名称. + */ + public static final String GROUP_FILE_NAME = "group"; + /** + * 网关能够被访问的API的缓存文件名称. + */ + public static final String API_FILE_NAME = "api"; + /** + * 网关分组绑定的插件缓存文件名称. + */ + public static final String PLUGIN_FILE_NAME = "plugin"; + /** + * 网关分组绑定的插件缓存文件名称. + */ + public static final String PATH_REWRITE_FILE_NAME = "rewrite"; + /** + * PATH_REWRITE_EXCEPTION. + */ + public static final String PATH_REWRITE_EXCEPTION = "PathRewriteException"; + /** + * 网关路径通配规则缓存文件名称. + */ + public static final String PATH_WILDCARD_FILE_NAME = "wildcard"; + /** + * 本地文件的文件扩展名称. + */ + public static final String FILE_SUFFIX = ".json"; + /** + * 网关插件通用部署组名称. + */ + public static final String GATEWAY_COMMON_DEPLOY_GROUP_ID = "common"; + /** + * 在Spring Cloud Gateway中用来全量匹配所有请求的. + */ + public static final String GATEWAY_WILDCARD_SERVICE_NAME = "wildcard_tsf_gateway"; + /** + * Tsf Gateway 的限流标签. + */ + public static final String TSF_GATEWAY_RATELIMIT_CONTEXT_TAG = "tsf-gateway-ratelimit-context"; + /** + * 本地文件的仓库跟目录. + */ + private static final String GATEWAY_REPO_ROOT = System.getProperty("user.home"); + /** + * Gateway 本地仓库的文件目录. + */ + public static final String GATEWAY_REPO_PREFIX = GATEWAY_REPO_ROOT + "/tsf/gateway/"; + + private GatewayConstant() { + + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/HeaderName.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/HeaderName.java new file mode 100644 index 000000000..b1c7023be --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/HeaderName.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.constant; + +public final class HeaderName { + /** + * UNIT. + */ + public static final String UNIT = "TSF-Unit"; + /** + * NAMESPACE_ID. + */ + public static final String NAMESPACE_ID = "TSF-NamespaceId"; + /** + * APP_KEY. + */ + public static final String APP_KEY = "x-mg-secretid"; + /** + * ALG. + */ + public static final String ALG = "x-mg-alg"; + /** + * SIGN. + */ + public static final String SIGN = "x-mg-sign"; + /** + * NONCE. + */ + public static final String NONCE = "x-mg-nonce"; + /** + * NODE. + */ + public static final String NODE = "x-mg-node"; + /** + * TRACE_ID. + */ + public static final String TRACE_ID = "x-mg-traceid"; + + + private HeaderName() { + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/HttpMethod.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/HttpMethod.java new file mode 100644 index 000000000..9052a37c4 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/HttpMethod.java @@ -0,0 +1,56 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.constant; + +/** + * @author kysonli + * 2019/4/11 11:54 + */ +public enum HttpMethod { + /** + * POST. + */ + POST("POST"), + /** + * GET. + */ + GET("GET"), + /** + * PUT. + */ + PUT("PUT"), + /** + * DELETE. + */ + DELETE("DELETE"); + + private final String httpMethod; + + HttpMethod(String httpMethod) { + this.httpMethod = httpMethod; + } + + public static HttpMethod getHttpMethod(String method) { + for (HttpMethod httpMethod : HttpMethod.values()) { + if (httpMethod.httpMethod.equalsIgnoreCase(method)) { + return httpMethod; + } + } + return null; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginConstants.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginConstants.java new file mode 100644 index 000000000..727ea75f6 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginConstants.java @@ -0,0 +1,68 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.constant; + +import java.util.Optional; + +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; + +/** + * @author: vmershen + * @since: 1.1.0 + **/ +public final class PluginConstants { + /** + * 设置每个标签插件JSON串长度. + */ + public static final Integer TAG_PLUGIN_INFO_LIST_LIMIT = 3000; + + private PluginConstants() { + + } + + /** + * TAG插件traceIdEnabled类型. + */ + public enum TraceIdEnabledType { + /** + * Y: enable. + */ + Y, + /** + * N: disable. + */ + N; + + public static TraceIdEnabledType getTraceIdEnabledType(String enabledType) { + for (TraceIdEnabledType taskFlowEdgeType : TraceIdEnabledType.values()) { + if (taskFlowEdgeType.name().equalsIgnoreCase(enabledType)) { + return taskFlowEdgeType; + } + } + return null; + } + + + public static void checkValidity(String enabledType) { + TraceIdEnabledType traceIdEnabledType = getTraceIdEnabledType(enabledType); + Optional.ofNullable(traceIdEnabledType).orElseThrow(() + -> new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_INVALID, "Tag插件TraceIdEnabled类型")); + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginScopeType.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginScopeType.java new file mode 100644 index 000000000..cd9951c24 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginScopeType.java @@ -0,0 +1,54 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.constant; + +/** + * 插件范围类型. + * @author seanlxliu + * @since 2019/9/10 + */ +public enum PluginScopeType { + /** + * 绑定组. + */ + GROUP("group"), + + /** + * 绑定API. + */ + API("api"); + + private final String scopeType; + + PluginScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public static PluginScopeType getScopeType(String scopeType) { + for (PluginScopeType pluginScopeType : PluginScopeType.values()) { + if (pluginScopeType.scopeType.equals(scopeType)) { + return pluginScopeType; + } + } + return null; + } + + public String getScopeType() { + return scopeType; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginType.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginType.java new file mode 100644 index 000000000..3558b7c3d --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/PluginType.java @@ -0,0 +1,99 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.constant; + +import java.lang.invoke.MethodHandles; +import java.util.HashMap; +import java.util.Map; + +import com.tencent.tsf.gateway.core.model.JwtPlugin; +import com.tencent.tsf.gateway.core.model.OAuthPlugin; +import com.tencent.tsf.gateway.core.model.PluginInfo; +import com.tencent.tsf.gateway.core.model.RequestTransformerPlugin; +import com.tencent.tsf.gateway.core.model.TagPlugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public enum PluginType { + /** + * ReqTransformer 插件实现. + */ + REQ_TRANSFORMER("ReqTransformer", RequestTransformerPlugin.class), + + /** + * OAuth 插件实现. + */ + OAUTH("OAuth", OAuthPlugin.class), + /** + * Jwt 插件实现. + */ + JWT("Jwt", JwtPlugin.class), + /** + * Tag 转化插件实现. + */ + TAG("Tag", TagPlugin.class); + + private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private static final Map pluginMap = new HashMap<>(); + + static { + pluginMap.put(OAUTH.name(), OAUTH.type); + pluginMap.put(JWT.name(), JWT.type); + pluginMap.put(TAG.name(), TAG.type); + } + + private String type; + private Class pluginClazz; + + PluginType(String type, Class pluginClazz) { + this.type = type; + this.pluginClazz = pluginClazz; + } + + public static PluginType getPluginType(String name) { + for (PluginType pluginType : PluginType.values()) { + if (pluginType.type.equalsIgnoreCase(name)) { + return pluginType; + } + } + logger.warn("Unknown plugin type exception, please upgrade your gateway sdk"); + return null; + } + + public static Map toMap() { + return pluginMap; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Class getPluginClazz() { + return pluginClazz; + } + + public void setPluginClazz(Class pluginClazz) { + this.pluginClazz = pluginClazz; + } + +} + diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/TsfAlgType.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/TsfAlgType.java new file mode 100644 index 000000000..991008ca0 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/constant/TsfAlgType.java @@ -0,0 +1,69 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.constant; + +/** + * @author kysonli + * 2019/4/11 11:42 + */ +public enum TsfAlgType { + /** + * 0:HMAC_MD5. + */ + HMAC_MD5("0"), + /** + * 1:HMAC_SHA_1. + */ + HMAC_SHA_1("1"), + /** + * 2:HMAC_SHA_256. + */ + HMAC_SHA_256("2"), + /** + * 3:HMAC_SHA_512. + */ + HMAC_SHA_512("3"), + /** + * 4:HMAC_SM3. + */ + HMAC_SM3("4"); + + + private String alg; + + TsfAlgType(String code) { + this.alg = code; + } + + public static TsfAlgType getSecurityCode(String code) { + for (TsfAlgType algType : TsfAlgType.values()) { + if (algType.alg.equals(code)) { + return algType; + } + } + return null; + } + + public String getAlg() { + return alg; + } + + public void setAlg(String alg) { + this.alg = alg; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/exception/TsfGatewayError.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/exception/TsfGatewayError.java new file mode 100644 index 000000000..2b3e9bbcd --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/exception/TsfGatewayError.java @@ -0,0 +1,142 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.exception; + +/** + * @author kysonli + * 2019/4/9 13:30 + */ +public enum TsfGatewayError { + /** + * GATEWAY_INTERNAL_ERROR. + */ + GATEWAY_INIT_ERROR(5000, "InitializeError: %s", 500), + /** + * GATEWAY_SYNC_ERROR. + */ + GATEWAY_SYNC_ERROR(5001, "SyncDataError: %s", 500), + /** + * GATEWAY_FILE_ERROR. + */ + GATEWAY_FILE_ERROR(5002, "LocalFile IO Error", 500), + /** + * GATEWAY_SERIALIZE_ERROR. + */ + GATEWAY_SERIALIZE_ERROR(5003, "SerializeError: %s", 500), + /** + * GATEWAY_AUTH_ERROR. + */ + GATEWAY_AUTH_ERROR(5004, "AuthCheckException: %s", 500), + /** + * GATEWAY_INTERNAL_ERROR. + */ + GATEWAY_INTERNAL_ERROR(5005, "Gateway Internal Error: %s", 500), + /** + * GATEWAY_PARAMETER_REQUIRED. + */ + GATEWAY_PARAMETER_REQUIRED(5006, "ParameterRequired: %s", 500), + /** + * GATEWAY_PARAMETER_INVALID. + */ + GATEWAY_PARAMETER_INVALID(5007, "ParameterInvalid: %s", 500), + /** + * GATEWAY_BAD_REQUEST. + */ + GATEWAY_BAD_REQUEST(4000, "BadRequest: %s", 400), + /** + * GATEWAY_AUTH_FAILED. + */ + GATEWAY_AUTH_FAILED(4001, "AuthCheckFailed: %s", 401), + /** + * GATEWAY_REQUEST_FORBIDDEN. + */ + GATEWAY_REQUEST_FORBIDDEN(4003, "RequestForbidden: %s", 403), + /** + * GATEWAY_REQUEST_NOT_FOUND. + */ + GATEWAY_REQUEST_NOT_FOUND(4004, "RequestNotFound: %s", 404), + /** + * GATEWAY_REQUEST_METHOD_NOT_ALLOWED. + */ + GATEWAY_REMOTE_REQUEST_INVALID(4016, "RemoteRequestInvalid: %s", 416), + /** + * GATEWAY_REMOTE_SERVER_AUTH_FAILED. + */ + GATEWAY_REMOTE_SERVER_AUTH_FAILED(4017, "RemoteServerAuthFailed: %s", 401), + /** + * GATEWAY_PLUGIN_JWT. + */ + GATEWAY_PLUGIN_JWT(4020, "JwtError: %s", 500), + /** + * GATEWAY_REMOTE_SERVER_BUSY. + */ + GATEWAY_REMOTE_SERVER_BUSY(4021, "Server is busy: %s", 401), + /** + * GATEWAY_REMOTE_SERVER_CODE_INVALID. + */ + GATEWAY_REMOTE_SERVER_CODE_INVALID(4022, "Login Code is error: %s", 401), + /** + * GATEWAY_REMOTE_SERVER_LIMIT. + */ + GATEWAY_REMOTE_SERVER_LIMIT(4023, "Server Limit : %s", 401), + /** + * GATEWAY_AUTH_EXPIRED. + */ + GATEWAY_AUTH_EXPIRED(4024, "AuthCheckExpired : %s", 401), + /** + * GATEWAY_TOO_MANY_REQUEST. + */ + GATEWAY_TOO_MANY_REQUEST(4025, "AuthCheckExpired : %s", 429); + + + private Integer code; + + private String errMsg; + + private Integer httpStatus; + + TsfGatewayError(Integer code, String errMsg, Integer httpStatus) { + this.code = code; + this.errMsg = errMsg; + this.httpStatus = httpStatus; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public String getErrMsg() { + return errMsg; + } + + public void setErrMsg(String errMsg) { + this.errMsg = errMsg; + } + + public Integer getHttpStatus() { + return httpStatus; + } + + public void setHttpStatus(Integer httpStatus) { + this.httpStatus = httpStatus; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/exception/TsfGatewayException.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/exception/TsfGatewayException.java new file mode 100644 index 000000000..c47c48928 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/exception/TsfGatewayException.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +/** + * @author kysonli + * 2019/4/9 12:29 + */ +public class TsfGatewayException extends ResponseStatusException { + private static final long serialVersionUID = 9210499285923741857L; + + private final TsfGatewayError gatewayError; + + public TsfGatewayException(TsfGatewayError gatewayError, Object... args) { + this(gatewayError, null, args); + } + + public TsfGatewayException(TsfGatewayError gatewayError, Throwable throwable, Object... args) { + super(HttpStatus.resolve(gatewayError.getHttpStatus()), String.format("%s", String.format(gatewayError.getErrMsg(), args)), throwable); + this.gatewayError = gatewayError; + } + + public TsfGatewayError getGatewayError() { + return gatewayError; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/http/HttpConfigConstant.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/http/HttpConfigConstant.java new file mode 100644 index 000000000..ee73f7ee5 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/http/HttpConfigConstant.java @@ -0,0 +1,52 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.http; + +/** + * @ClassName Config + * @Description TODO + * @Author vmershen + * @Date 2019/7/8 11:48 + * @Version 1.0 + */ +public final class HttpConfigConstant { + /** + * HTTP_CONNECT_TIMEOUT. + */ + public static final int HTTP_CONNECT_TIMEOUT = 2000; + /** + * HTTP_SOCKET_TIMEOUT. + */ + public static final int HTTP_SOCKET_TIMEOUT = 10000; + /** + * HTTP_MAX_POOL_SIZE. + */ + public static final int HTTP_MAX_POOL_SIZE = 200; + /** + * HTTP_MONITOR_INTERVAL. + */ + public static final int HTTP_MONITOR_INTERVAL = 5000; + /** + * HTTP_IDLE_TIMEOUT. + */ + public static final int HTTP_IDLE_TIMEOUT = 30000; + + private HttpConfigConstant() { + + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/http/HttpConnectionPoolUtil.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/http/HttpConnectionPoolUtil.java new file mode 100644 index 000000000..e46c6d612 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/http/HttpConnectionPoolUtil.java @@ -0,0 +1,345 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.http; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.lang.invoke.MethodHandles; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TimerTask; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; + +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.polaris.client.util.NamedThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import shade.polaris.org.apache.org.apache.http.HttpEntityEnclosingRequest; +import shade.polaris.org.apache.org.apache.http.HttpHost; +import shade.polaris.org.apache.org.apache.http.HttpRequest; +import shade.polaris.org.apache.org.apache.http.NoHttpResponseException; +import shade.polaris.org.apache.org.apache.http.client.HttpRequestRetryHandler; +import shade.polaris.org.apache.org.apache.http.client.config.RequestConfig; +import shade.polaris.org.apache.org.apache.http.client.methods.CloseableHttpResponse; +import shade.polaris.org.apache.org.apache.http.client.methods.HttpGet; +import shade.polaris.org.apache.org.apache.http.client.methods.HttpPost; +import shade.polaris.org.apache.org.apache.http.client.methods.HttpRequestBase; +import shade.polaris.org.apache.org.apache.http.client.protocol.HttpClientContext; +import shade.polaris.org.apache.org.apache.http.client.utils.URLEncodedUtils; +import shade.polaris.org.apache.org.apache.http.config.Registry; +import shade.polaris.org.apache.org.apache.http.config.RegistryBuilder; +import shade.polaris.org.apache.org.apache.http.conn.ConnectTimeoutException; +import shade.polaris.org.apache.org.apache.http.conn.routing.HttpRoute; +import shade.polaris.org.apache.org.apache.http.conn.socket.ConnectionSocketFactory; +import shade.polaris.org.apache.org.apache.http.conn.socket.LayeredConnectionSocketFactory; +import shade.polaris.org.apache.org.apache.http.conn.socket.PlainConnectionSocketFactory; +import shade.polaris.org.apache.org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import shade.polaris.org.apache.org.apache.http.entity.StringEntity; +import shade.polaris.org.apache.org.apache.http.impl.client.CloseableHttpClient; +import shade.polaris.org.apache.org.apache.http.impl.client.HttpClients; +import shade.polaris.org.apache.org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import shade.polaris.org.apache.org.apache.http.message.BasicNameValuePair; +import shade.polaris.org.apache.org.apache.http.protocol.HttpContext; +import shade.polaris.org.apache.org.apache.http.util.EntityUtils; + + +/** + * @ClassName HttpConnectionPoolUtil + * @Description httpclient连接池工具类 + * @Author vmershen + * @Date 2019/7/8 11:56 + * @Version 1.0 + */ +public final class HttpConnectionPoolUtil { + private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private static final int CONNECT_TIMEOUT = HttpConfigConstant.HTTP_CONNECT_TIMEOUT; + private static final int SOCKET_TIMEOUT = HttpConfigConstant.HTTP_SOCKET_TIMEOUT; + private static final int MAX_CONN = HttpConfigConstant.HTTP_MAX_POOL_SIZE; + private static final int MAX_PRE_ROUTE = HttpConfigConstant.HTTP_MAX_POOL_SIZE; + private static final int MAX_ROUTE = HttpConfigConstant.HTTP_MAX_POOL_SIZE; + private final static Object syncLock = new Object(); + private volatile static CloseableHttpClient httpClient; + private static PoolingHttpClientConnectionManager manager; + private static ScheduledExecutorService monitorExecutor; + + //程序退出时,释放资源 + static { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + closeConnectionPool(); + } + }); + } + + private HttpConnectionPoolUtil() { + + } + + private static void setRequestConfig(HttpRequestBase httpRequestBase, Integer timeout) { + RequestConfig requestConfig = RequestConfig + .custom() + .setConnectionRequestTimeout(timeout == null ? CONNECT_TIMEOUT : timeout) + .setConnectTimeout(timeout == null ? CONNECT_TIMEOUT : timeout) + .setSocketTimeout(timeout == null ? SOCKET_TIMEOUT : timeout) + .build(); + httpRequestBase.setConfig(requestConfig); + } + + public static CloseableHttpClient getHttpClient(String url) { + logger.info("url is : {}", url); + if (httpClient == null) { + //多线程下多个线程同时调用getHttpClient容易导致重复创建httpClient对象的问题,所以加上了同步锁 + synchronized (syncLock) { + if (httpClient == null) { + httpClient = createHttpClient(url); + //开启监控线程,对异常和空闲线程进行关闭 + monitorExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("gw-client", true)); + monitorExecutor.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + //关闭异常连接 + manager.closeExpiredConnections(); + //关闭5s空闲的连接 + manager.closeIdleConnections(HttpConfigConstant.HTTP_IDLE_TIMEOUT, TimeUnit.MILLISECONDS); + logger.debug("close expired and idle for over {} ms connection", HttpConfigConstant.HTTP_IDLE_TIMEOUT); + } + }, HttpConfigConstant.HTTP_MONITOR_INTERVAL, HttpConfigConstant.HTTP_MONITOR_INTERVAL, TimeUnit.MILLISECONDS); + } + } + } + return httpClient; + } + + public static CloseableHttpClient createHttpClient(String url) { + ConnectionSocketFactory plainSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); + LayeredConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory(); + Registry registry = RegistryBuilder.create() + .register("http", plainSocketFactory) + .register("https", sslSocketFactory).build(); + manager = new PoolingHttpClientConnectionManager(registry); + //设置连接参数 + manager.setMaxTotal(MAX_CONN); // 最大连接数 + manager.setDefaultMaxPerRoute(MAX_PRE_ROUTE); // 路由最大连接数 + //HttpHost httpHost = new HttpHost(host, port); + HttpHost httpHost = new HttpHost(url); + manager.setMaxPerRoute(new HttpRoute(httpHost), MAX_ROUTE); + //请求失败时,进行请求重试 + HttpRequestRetryHandler handler = new HttpRequestRetryHandler() { + @Override + public boolean retryRequest(IOException e, int i, HttpContext httpContext) { + if (i > 3) { + //重试超过3次,放弃请求 + logger.error("retry has more than 3 time, give up request"); + return false; + } + if (e instanceof NoHttpResponseException) { + //服务器没有响应,可能是服务器断开了连接,应该重试 + logger.error("receive no response from server, retry"); + return true; + } + if (e instanceof SSLHandshakeException) { + // SSL握手异常 + logger.error("SSL hand shake exception"); + return false; + } + if (e instanceof InterruptedIOException) { + //超时 + logger.error("InterruptedIOException"); + return false; + } + if (e instanceof UnknownHostException) { + // 服务器不可达 + logger.error("server host unknown"); + return false; + } + if (e instanceof ConnectTimeoutException) { + // 连接超时 + logger.error("Connection Time out"); + return false; + } + if (e instanceof SSLException) { + logger.error("SSLException"); + return false; + } + HttpClientContext context = HttpClientContext.adapt(httpContext); + HttpRequest request = context.getRequest(); + //如果请求不是关闭连接的请求 + return !(request instanceof HttpEntityEnclosingRequest); + } + }; + CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).setRetryHandler(handler) + .build(); + return client; + } + + /** + * get方式,请求参数以?形式拼接在url后面. + * + * @param url 请求url + * @param paramsMap 请求参数 + * @param headerParamsMap 传入的header参数 + * @param timeout 单位毫秒 + * @return 返回结果 + */ + public static String httpGet(String url, Map paramsMap, Map headerParamsMap, Integer timeout) { + // 数据必填项校验 + if (StringUtils.isEmpty(url)) { + throw new IllegalArgumentException("url can not be empty"); + } + CloseableHttpResponse res = null; + CloseableHttpClient httpClient = getHttpClient(url); + String result = null; + String baseUrl = buildUrl(url, paramsMap); + logger.info("get request: {}", baseUrl); + HttpGet httpGet = new HttpGet(baseUrl); + setRequestConfig(httpGet, timeout); + httpGet.addHeader("Content-type", "application/json; charset=utf-8"); + httpGet.setHeader("Accept", "application/json"); + // 添加传入的header参数 + buildHeaderParams(httpGet, headerParamsMap); + try { + res = httpClient.execute(httpGet, HttpClientContext.create()); + result = EntityUtils.toString(res.getEntity()); + logger.info("get response :{}", result); + if (res.getStatusLine().getStatusCode() != 200) { + logger.info("response error: {}", result); + throw new IllegalStateException(String.format("call url: %s failed, response: code[%s] body[%s]", + baseUrl, res.getStatusLine().getStatusCode(), result)); + } + return result; + } + catch (IOException e) { + logger.warn("Get request failed, url:" + baseUrl, e); + throw new RuntimeException(e); + } + finally { + closeHttpResponse(res); + } + } + + private static void closeHttpResponse(CloseableHttpResponse res) { + try { + if (res != null) { + res.close(); + } + } + catch (IOException e) { + logger.warn("Close httpClient failed!", e); + throw new RuntimeException(e); + } + } + + /** + * 关闭连接池. + */ + public static void closeConnectionPool() { + try { + httpClient.close(); + manager.close(); + monitorExecutor.shutdown(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + public static String httpPostWithJSON(String url, Map paramsMap, String json, Map headerParamsMap, Integer timeout) + throws Exception { + // 数据必填项校验 + if (StringUtils.isBlank(url)) { + throw new Exception("url can't be empty"); + } + // 数据必填项校验 + if (StringUtils.isBlank(json)) { + json = ""; + } + String baseUrl = buildUrl(url, paramsMap); + String result = null; + CloseableHttpResponse res = null; + CloseableHttpClient httpClient = getHttpClient(url); + HttpPost httpPost = new HttpPost(baseUrl); + setRequestConfig(httpPost, timeout); + httpPost.addHeader("Content-type", "application/json; charset=utf-8"); + httpPost.setHeader("Accept", "application/json"); + // 添加传入的header参数 + buildHeaderParams(httpPost, headerParamsMap); + + httpPost.setEntity(new StringEntity(json, StandardCharsets.UTF_8)); + logger.info("post url: {}", url); + try { + res = httpClient.execute(httpPost, HttpClientContext.create()); + result = EntityUtils.toString(res.getEntity()); + if (res.getStatusLine().getStatusCode() != 200 && res.getStatusLine().getStatusCode() != 201) { + logger.info("response error: {}", result); + throw new IllegalStateException(String.format("call url: %s failed, response: code[%s] body[%s]", + baseUrl, res.getStatusLine().getStatusCode(), result)); + } + return result; + } + catch (IOException e) { + logger.warn("Get request failed, url:" + baseUrl, e); + throw new RuntimeException(e); + } + finally { + closeHttpResponse(res); + } + } + + private static String buildUrl(String url, Map paramsMap) { + String baseUrl = null; + if (paramsMap != null && !paramsMap.isEmpty()) { + List params = new ArrayList(); + for (Map.Entry entry : paramsMap.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (value != null) { + params.add(new BasicNameValuePair(key, value)); + } + } + if (url.contains("?")) { + baseUrl = url + "&" + URLEncodedUtils.format(params, "UTF-8"); + } + else { + baseUrl = url + "?" + URLEncodedUtils.format(params, "UTF-8"); + } + } + else { + baseUrl = url; + } + return baseUrl; + } + + private static void buildHeaderParams(HttpRequestBase httpRequestBase, Map headerParamsMap) { + if (null != headerParamsMap) { + for (Map.Entry entry : headerParamsMap.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + httpRequestBase.setHeader(key, value); + } + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/ClaimMapping.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/ClaimMapping.java new file mode 100644 index 000000000..511a5e2eb --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/ClaimMapping.java @@ -0,0 +1,63 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.io.Serializable; + +/** + * @ClassName ClaimMapping + * @Description TODO + * @Author vmershen + * @Date 2019/7/12 16:12 + * @Version 1.0 + */ +public class ClaimMapping implements Serializable { + + + private static final long serialVersionUID = -8768365572845806890L; + //参数名称 + private String parameterName; + //映射后参数名称 + private String mappingParameterName; + //映射后参数名称 + private String location; + + public String getParameterName() { + return parameterName; + } + + public void setParameterName(String parameterName) { + this.parameterName = parameterName; + } + + public String getMappingParameterName() { + return mappingParameterName; + } + + public void setMappingParameterName(String mappingParameterName) { + this.mappingParameterName = mappingParameterName; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GatewayAllResult.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GatewayAllResult.java new file mode 100644 index 000000000..964053bde --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GatewayAllResult.java @@ -0,0 +1,70 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + + +public class GatewayAllResult { + + private GroupResult groupResult; + + private GroupApiResult groupApiResult; + + private PathRewriteResult pathRewriteResult; + + private PathWildcardResult pathWildcardResult; + + public GatewayAllResult(GroupResult groupResult, GroupApiResult groupApiResult, + PathRewriteResult pathRewriteResult, PathWildcardResult pathWildcardResult) { + this.groupResult = groupResult; + this.groupApiResult = groupApiResult; + this.pathRewriteResult = pathRewriteResult; + this.pathWildcardResult = pathWildcardResult; + } + + public GroupResult getGroupResult() { + return groupResult; + } + + public void setGroupResult(GroupResult groupResult) { + this.groupResult = groupResult; + } + + public GroupApiResult getGroupApiResult() { + return groupApiResult; + } + + public void setGroupApiResult(GroupApiResult groupApiResult) { + this.groupApiResult = groupApiResult; + } + + public PathRewriteResult getPathRewriteResult() { + return pathRewriteResult; + } + + public void setPathRewriteResult(PathRewriteResult pathRewriteResult) { + this.pathRewriteResult = pathRewriteResult; + } + + public PathWildcardResult getPathWildcardResult() { + return pathWildcardResult; + } + + public void setPathWildcardResult(PathWildcardResult pathWildcardResult) { + this.pathWildcardResult = pathWildcardResult; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GatewayResult.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GatewayResult.java new file mode 100644 index 000000000..e7024fc3d --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GatewayResult.java @@ -0,0 +1,89 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.io.Serializable; +import java.util.List; + +/** + * @author kysonli + * 2019/4/10 11:14 + */ +public class GatewayResult implements Serializable { + private static final long serialVersionUID = -8391900871963415134L; + + private String gatewayId; + + private String gatewayName; + + private String gatewayGroupId; + + private Integer reversion; + + private String updatedTime; + + private List result; + + public String getGatewayId() { + return gatewayId; + } + + public void setGatewayId(String gatewayId) { + this.gatewayId = gatewayId; + } + + public String getGatewayName() { + return gatewayName; + } + + public void setGatewayName(String gatewayName) { + this.gatewayName = gatewayName; + } + + public String getGatewayGroupId() { + return gatewayGroupId; + } + + public void setGatewayGroupId(String gatewayGroupId) { + this.gatewayGroupId = gatewayGroupId; + } + + public Integer getReversion() { + return reversion; + } + + public void setReversion(Integer reversion) { + this.reversion = reversion; + } + + public String getUpdatedTime() { + return updatedTime; + } + + public void setUpdatedTime(String updatedTime) { + this.updatedTime = updatedTime; + } + + public List getResult() { + return result; + } + + public void setResult(List result) { + this.result = result; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/Group.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/Group.java new file mode 100644 index 000000000..3ad738d41 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/Group.java @@ -0,0 +1,171 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.io.Serializable; +import java.util.List; +import java.util.Locale; + +import com.tencent.cloud.plugin.gateway.context.Position; + +/** + * @author kysonli + * 2019/4/10 12:23 + */ +public class Group implements Serializable { + private static final long serialVersionUID = -7714152839551413735L; + + private String groupId; + + private String groupName; + + private String groupContext; + + private String releaseStatus; + + private String authMode; + + private String groupType; + + private List secretList; + + /** + * 命名空间参数key值. + */ + private String namespaceNameKey; + + /** + * 微服务名参数key值. + */ + private String serviceNameKey; + + /** + * 命名空间参数位置,Path,Header或Query,默认是Path. + */ + private String namespaceNameKeyPosition = Position.PATH.name().toLowerCase(Locale.ROOT); + + /** + * 微服务名参数位置,Path,Header或Query,默认是Path. + */ + private String serviceNameKeyPosition = Position.PATH.name().toLowerCase(Locale.ROOT); + + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public String getGroupContext() { + return groupContext; + } + + public void setGroupContext(String groupContext) { + this.groupContext = groupContext; + } + + public String getReleaseStatus() { + return releaseStatus; + } + + public void setReleaseStatus(String releaseStatus) { + this.releaseStatus = releaseStatus; + } + + public String getAuthMode() { + return authMode; + } + + public void setAuthMode(String authMode) { + this.authMode = authMode; + } + + public String getGroupType() { + return groupType; + } + + public void setGroupType(String groupType) { + this.groupType = groupType; + } + + public List getSecretList() { + return secretList; + } + + public void setSecretList(List secretList) { + this.secretList = secretList; + } + + public String getNamespaceNameKey() { + return namespaceNameKey; + } + + public void setNamespaceNameKey(String namespaceNameKey) { + this.namespaceNameKey = namespaceNameKey; + } + + public String getServiceNameKey() { + return serviceNameKey; + } + + public void setServiceNameKey(String serviceNameKey) { + this.serviceNameKey = serviceNameKey; + } + + public String getNamespaceNameKeyPosition() { + return namespaceNameKeyPosition; + } + + public void setNamespaceNameKeyPosition(String namespaceNameKeyPosition) { + this.namespaceNameKeyPosition = namespaceNameKeyPosition; + } + + public String getServiceNameKeyPosition() { + return serviceNameKeyPosition; + } + + public void setServiceNameKeyPosition(String serviceNameKeyPosition) { + this.serviceNameKeyPosition = serviceNameKeyPosition; + } + + @Override + public String toString() { + return "Group{" + + "groupId='" + groupId + '\'' + + ", groupName='" + groupName + '\'' + + ", groupContext='" + groupContext + '\'' + + ", releaseStatus='" + releaseStatus + '\'' + + ", authMode='" + authMode + '\'' + + ", groupType='" + groupType + '\'' + + ", secretList=" + secretList + + ", namespaceNameKey='" + namespaceNameKey + '\'' + + ", serviceNameKey='" + serviceNameKey + '\'' + + ", namespaceNameKeyPosition='" + namespaceNameKeyPosition + '\'' + + ", serviceNameKeyPosition='" + serviceNameKeyPosition + '\'' + + '}'; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupApi.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupApi.java new file mode 100644 index 000000000..c1edbc312 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupApi.java @@ -0,0 +1,221 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * @author kysonli + * 2019/4/10 12:20 + */ +public class GroupApi implements Serializable { + private static final long serialVersionUID = 5772774534522075805L; + + private String apiId; + + private String groupId; + + private String path; + + private String method; + + private String serviceName; + + private String namespaceId; + + private String namespaceName; + + private String releaseStatus; + + private String usableStatus; + + private String pathMapping; + + private Integer timeout; + + + /** + * api所在服务host,格式: http://localhost:8080. + */ + private String host; + + /** + * 描述信息. + */ + private String description; + + /** + * API类型, ms : 微服务API; external :外部服务Api. + */ + private String apiType; + + /** + * RPC 类型,http(spring cloud), dubbo ... + */ + private String rpcType; + + /** + * RPC 额外信息,json 字符串,根据不同的 rpcType 进行解析. + */ + private String rpcExt; + + + /** + * rpcExt反序列化对应的对象. + */ + @JsonIgnore + private Object rpcExtObj; + + public String getApiId() { + return apiId; + } + + public void setApiId(String apiId) { + this.apiId = apiId; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getNamespaceId() { + return namespaceId; + } + + public void setNamespaceId(String namespaceId) { + this.namespaceId = namespaceId; + } + + public String getNamespaceName() { + return namespaceName; + } + + public void setNamespaceName(String namespaceName) { + this.namespaceName = namespaceName; + } + + public String getReleaseStatus() { + return releaseStatus; + } + + public void setReleaseStatus(String releaseStatus) { + this.releaseStatus = releaseStatus; + } + + public String getUsableStatus() { + return usableStatus; + } + + public void setUsableStatus(String usableStatus) { + this.usableStatus = usableStatus; + } + + public String getPathMapping() { + return pathMapping; + } + + public void setPathMapping(String pathMapping) { + this.pathMapping = pathMapping; + } + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getApiType() { + return apiType; + } + + public void setApiType(String apiType) { + this.apiType = apiType; + } + + public String getRpcType() { + return rpcType; + } + + public void setRpcType(String rpcType) { + this.rpcType = rpcType; + } + + public String getRpcExt() { + return rpcExt; + } + + public void setRpcExt(String rpcExt) { + this.rpcExt = rpcExt; + } + + public Object getRpcExtObj() { + return rpcExtObj; + } + + public void setRpcExtObj(Object rpcExtObj) { + this.rpcExtObj = rpcExtObj; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupApiResult.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupApiResult.java new file mode 100644 index 000000000..3e7266801 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupApiResult.java @@ -0,0 +1,26 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +/** + * @author kysonli + * 2019/4/10 10:18 + */ +public class GroupApiResult extends GatewayResult { + private static final long serialVersionUID = -408021255097231992L; +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupResult.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupResult.java new file mode 100644 index 000000000..3ff088dc1 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupResult.java @@ -0,0 +1,26 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +/** + * @author kysonli + * 2019/4/10 10:17 + */ +public class GroupResult extends GatewayResult { + private static final long serialVersionUID = -7882585922852119178L; +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupSecret.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupSecret.java new file mode 100644 index 000000000..48bc99801 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/GroupSecret.java @@ -0,0 +1,104 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.io.Serializable; + +/** + * @author kysonli + * 2019/4/10 10:18 + */ +public class GroupSecret implements Serializable { + + private static final long serialVersionUID = 4619764526878050813L; + + + private String secretId; + + /** + * 秘钥值. + */ + private String secretKey; + + /** + * 秘钥名称. + */ + private String secretName; + + /** + * API 分组ID. + */ + private String groupId; + + /** + * 启用/禁用. + */ + private String status; + + + private String expiredTime; + + + public String getSecretId() { + return secretId; + } + + public void setSecretId(String secretId) { + this.secretId = secretId; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public String getSecretName() { + return secretName; + } + + public void setSecretName(String secretName) { + this.secretName = secretName; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getExpiredTime() { + return expiredTime; + } + + public void setExpiredTime(String expiredTime) { + this.expiredTime = expiredTime; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/JwtPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/JwtPlugin.java new file mode 100644 index 000000000..9fae58ab2 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/JwtPlugin.java @@ -0,0 +1,119 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; + +/** + * @ClassName JwtPlugin + * @Description TODO + * @Author vmershen + * @Date 2019/7/3 17:15 + * @Version 1.0 + */ +public class JwtPlugin extends PluginInfo { + + private static final long serialVersionUID = 2238976166882026848L; + //公钥对kid + private String kid; + + //公钥对 + private String publicKeyJson; + + //token携带位置 + private String tokenBaggagePosition; + + //token的key值,校验参数名称 + private String tokenKeyName; + + //重定向地址,非必填 + private String redirectUrl; + + //claim参数映射关系json + private String claimMappingJson; + + public String getPublicKeyJson() { + return publicKeyJson; + } + + public void setPublicKeyJson(String publicKeyJson) { + this.publicKeyJson = publicKeyJson; + } + + public String getTokenBaggagePosition() { + return tokenBaggagePosition; + } + + public void setTokenBaggagePosition(String tokenBaggagePosition) { + this.tokenBaggagePosition = tokenBaggagePosition; + } + + public String getTokenKeyName() { + return tokenKeyName; + } + + public void setTokenKeyName(String tokenKeyName) { + this.tokenKeyName = tokenKeyName; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public void setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + } + + public String getKid() { + return kid; + } + + public void setKid(String kid) { + this.kid = kid; + } + + public String getClaimMappingJson() { + return claimMappingJson; + } + + public void setClaimMappingJson(String claimMappingJson) { + this.claimMappingJson = claimMappingJson; + } + + @Override + @JsonIgnore + public void check() { + super.check(); + if (StringUtils.isEmpty(publicKeyJson)) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "publicKeyJson"); + } + if (StringUtils.isEmpty(tokenBaggagePosition) || !(tokenBaggagePosition.equalsIgnoreCase("query") || + tokenBaggagePosition.equalsIgnoreCase("header"))) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenBaggagePosition"); + } + if (StringUtils.isEmpty(tokenKeyName)) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenKeyName"); + } + if (StringUtils.isEmpty(kid)) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "kid"); + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/OAuthPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/OAuthPlugin.java new file mode 100644 index 000000000..e642a041f --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/OAuthPlugin.java @@ -0,0 +1,166 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; + +/** + * @ClassName OAuthPlugin + * @Description OAuth插件类,必须保证改类的成员属性都是String类型,不可以当vo类使用 + * @Author vmershen + * @Date 2019/7/1 11:56 + * @Version 1.0 + */ +public class OAuthPlugin extends PluginInfo { + + private static final long serialVersionUID = -8269345448912085323L; + + //验证token微服务名(包含命名空间),当选择微服务时才传入值,值的格式为:命名空间/服务名 + //例如:ns-xxxx/provider-demo + private String tokenAuthServiceName; + + //验证token路径 + //1、当选择外部请求地址时,是完整url地址,例如:http://127.0.0.1:8080/oauth/token + //2、当选择微服务时,是微服务的方法path,例如:/oauth/token + private String tokenAuthUrl; + + //验证token请求方法 + private String tokenAuthMethod; + + //认证请求超时时间,单位:秒 范围:0~30 + private Integer expireTime; + + //重定向地址,非必填 + private String redirectUrl; + + //token携带位置,网关取token位置与发送认证请求时token位置一致 + private String tokenBaggagePosition; + + //token的key值 + private String tokenKeyName; + + //payload的映射参数名称 + private String payloadMappingName; + + //payload映射到后端服务的携带位置 + private String payloadMappingPosition; + + public String getTokenAuthServiceName() { + return tokenAuthServiceName; + } + + public void setTokenAuthServiceName(String tokenAuthServiceName) { + this.tokenAuthServiceName = tokenAuthServiceName; + } + + public String getTokenAuthUrl() { + return tokenAuthUrl; + } + + public void setTokenAuthUrl(String tokenAuthUrl) { + this.tokenAuthUrl = tokenAuthUrl; + } + + public String getTokenAuthMethod() { + return tokenAuthMethod; + } + + public void setTokenAuthMethod(String tokenAuthMethod) { + this.tokenAuthMethod = tokenAuthMethod; + } + + public Integer getExpireTime() { + return expireTime; + } + + public void setExpireTime(Integer expireTime) { + this.expireTime = expireTime; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public void setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + } + + public String getTokenBaggagePosition() { + return tokenBaggagePosition; + } + + public void setTokenBaggagePosition(String tokenBaggagePosition) { + this.tokenBaggagePosition = tokenBaggagePosition; + } + + public String getTokenKeyName() { + return tokenKeyName; + } + + public void setTokenKeyName(String tokenKeyName) { + this.tokenKeyName = tokenKeyName; + } + + public String getPayloadMappingName() { + return payloadMappingName; + } + + public void setPayloadMappingName(String payloadMappingName) { + this.payloadMappingName = payloadMappingName; + } + + public String getPayloadMappingPosition() { + return payloadMappingPosition; + } + + public void setPayloadMappingPosition(String payloadMappingPosition) { + this.payloadMappingPosition = payloadMappingPosition; + } + + @Override + @JsonIgnore + public void check() { + super.check(); + if (StringUtils.isEmpty(tokenAuthUrl)) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenAuthUrl"); + } + if (StringUtils.isEmpty(tokenAuthMethod) || !(tokenAuthMethod.equalsIgnoreCase("get") || + tokenAuthMethod.equalsIgnoreCase("post"))) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenAuthMethod"); + } + if (StringUtils.isEmpty(tokenBaggagePosition) || !(tokenBaggagePosition.equalsIgnoreCase("query") || + tokenBaggagePosition.equalsIgnoreCase("header"))) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenBaggagePosition"); + } + if (StringUtils.isEmpty(tokenKeyName)) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenKeyName"); + } + + if (!StringUtils.isEmpty(payloadMappingPosition) + && !payloadMappingPosition.equalsIgnoreCase("query") + && !payloadMappingPosition.equalsIgnoreCase("header")) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "payloadMappingPosition"); + } + if (expireTime == null || expireTime < 0 || expireTime > 30) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_INVALID, "expireTime"); + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/OAuthResult.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/OAuthResult.java new file mode 100644 index 000000000..19ff08eb5 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/OAuthResult.java @@ -0,0 +1,51 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.io.Serializable; + +/** + * @ClassName OAuthResult + * @Description TODO + * @Author vmershen + * @Date 2019/7/8 15:51 + * @Version 1.0 + */ +public class OAuthResult implements Serializable { + + + private static final long serialVersionUID = 863918261072626284L; + private Boolean result; + private String payload; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload) { + this.payload = payload; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathRewriteResult.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathRewriteResult.java new file mode 100644 index 000000000..76e6a01a2 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathRewriteResult.java @@ -0,0 +1,28 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import com.tencent.cloud.plugin.gateway.context.PathRewrite; + +/** + * @author seanlxliu + * @date 2020/5/19 + */ +public class PathRewriteResult extends GatewayResult { + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathWildcardResult.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathWildcardResult.java new file mode 100644 index 000000000..b5da134d7 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathWildcardResult.java @@ -0,0 +1,25 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +/** + * @author clarezzhang + */ +public class PathWildcardResult extends GatewayResult { + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathWildcardRule.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathWildcardRule.java new file mode 100644 index 000000000..44dc4d894 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PathWildcardRule.java @@ -0,0 +1,176 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * 路径通配规则. + * @author clarezzhang + */ +public class PathWildcardRule { + + /** + * 路径通配规则ID. + */ + private String wildCardId; + + /** + * 网关部署组ID. + */ + private String groupId; + + /** + * 通配路径. + */ + private String wildCardPath; + + /** + * 通配方法. + */ + private String method; + + /** + * 网关微服务ID. + */ + private String serviceId; + + /** + * 网关微服务名称. + */ + private String serviceName; + + /** + * 网关命名空间ID. + */ + private String namespaceId; + + /** + * 网关命名空间名称. + */ + private String namespaceName; + + /** + * 超时时间. + */ + private Integer timeout; + + /** + * 路径通配规则IDs. + */ + @JsonIgnore + private List wildCardIds; + + public String getWildCardId() { + return wildCardId; + } + + public void setWildCardId(String wildCardId) { + this.wildCardId = wildCardId; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getServiceId() { + return serviceId; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + public String getNamespaceId() { + return namespaceId; + } + + public void setNamespaceId(String namespaceId) { + this.namespaceId = namespaceId; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getNamespaceName() { + return namespaceName; + } + + public void setNamespaceName(String namespaceName) { + this.namespaceName = namespaceName; + } + + public String getWildCardPath() { + return wildCardPath; + } + + public void setWildCardPath(String wildCardPath) { + this.wildCardPath = wildCardPath; + } + + public List getWildCardIds() { + return wildCardIds; + } + + public void setWildCardIds(List wildCardIds) { + this.wildCardIds = wildCardIds; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + @Override + public String toString() { + return "PathWildcardRule{" + + "wildCardId='" + wildCardId + '\'' + + ", groupId='" + groupId + '\'' + + ", wildCardPath='" + wildCardPath + '\'' + + ", method='" + method + '\'' + + ", serviceId='" + serviceId + '\'' + + ", serviceName='" + serviceName + '\'' + + ", namespaceId='" + namespaceId + '\'' + + ", namespaceName='" + namespaceName + '\'' + + ", wildCardIds=" + wildCardIds + + ", timeout=" + timeout + + '}'; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginArgInfo.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginArgInfo.java new file mode 100644 index 000000000..e5824fc97 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginArgInfo.java @@ -0,0 +1,68 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.io.Serializable; + +public class PluginArgInfo implements Serializable { + private static final long serialVersionUID = -4811191577457792816L; + + //插件参数id + private String id; + + //插件id + private String pluginId; + + //插件属性名 + private String key; + + //插件属性值 + private Object value; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getPluginId() { + return pluginId; + } + + public void setPluginId(String pluginId) { + this.pluginId = pluginId; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginDetail.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginDetail.java new file mode 100644 index 000000000..0d05f84e7 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginDetail.java @@ -0,0 +1,60 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + + +import java.util.List; +import java.util.Objects; + +/** + * @ClassName PluginDetail + * @Description TODO + * @Author vmershen + * @Date 2019/7/2 11:53 + * @Version 1.0 + */ +public class PluginDetail extends PluginInfo { + + //插件参数 + private List pluginArgInfos; + + public List getPluginArgInfos() { + return pluginArgInfos; + } + + public void setPluginArgInfos(List pluginArgInfos) { + this.pluginArgInfos = pluginArgInfos; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PluginDetail)) { + return false; + } + PluginDetail that = (PluginDetail) o; + return Objects.equals(getId(), that.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(getPluginArgInfos()); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInfo.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInfo.java new file mode 100644 index 000000000..e5a30661e --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInfo.java @@ -0,0 +1,128 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; + +public class PluginInfo implements Serializable { + + private static final long serialVersionUID = -4823276300296184640L; + //网关插件id + private String id; + + //插件名称 + private String name; + + //插件类型 + private String type; + + //插件执行顺序 + private Integer order; + + //插件描述 + private String description; + + private String createdTime; + + private String updatedTime; + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCreatedTime() { + return createdTime; + } + + public void setCreatedTime(String createdTime) { + this.createdTime = createdTime; + } + + public String getUpdatedTime() { + return updatedTime; + } + + public void setUpdatedTime(String updatedTime) { + this.updatedTime = updatedTime; + } + + public Integer getOrder() { + return order; + } + + public void setOrder(Integer order) { + this.order = order; + } + + @JsonIgnore + public void check() { + if (StringUtils.isEmpty(name)) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "插件名称参数错误"); + } + if (StringUtils.isEmpty(type)) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "插件类型参数错误"); + } + } + + @Override + public String toString() { + return "PluginInfo{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", type='" + type + '\'' + + ", order=" + order + + ", description='" + description + '\'' + + ", createdTime='" + createdTime + '\'' + + ", updatedTime='" + updatedTime + '\'' + + '}'; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInstanceInfo.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInstanceInfo.java new file mode 100644 index 000000000..18bd84ceb --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInstanceInfo.java @@ -0,0 +1,71 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.io.Serializable; +import java.util.List; + +/** + * + * @author vmershen + * @date 2019/7/5 10:41 + */ +public class PluginInstanceInfo implements Serializable { + private static final long serialVersionUID = -8167195405736835024L; + + /** + * API或分组的ID. + */ + private String scopeValue; + + /** + * 插件绑定到的对象类型:group/api. + */ + private String scopeType; + + //分组绑定的插件列表 + private List pluginDetails; + + public static long getSerialVersionUID() { + return serialVersionUID; + } + + public String getScopeValue() { + return scopeValue; + } + + public void setScopeValue(String scopeValue) { + this.scopeValue = scopeValue; + } + + public List getPluginDetails() { + return pluginDetails; + } + + public void setPluginDetails(List pluginDetails) { + this.pluginDetails = pluginDetails; + } + + public String getScopeType() { + return scopeType; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInstanceInfoResult.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInstanceInfoResult.java new file mode 100644 index 000000000..b721ef263 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginInstanceInfoResult.java @@ -0,0 +1,26 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +/** + * @author seanlxliu + * @since 2019/9/11 + */ +public class PluginInstanceInfoResult extends GatewayResult { + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginPayload.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginPayload.java new file mode 100644 index 000000000..40e90f297 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/PluginPayload.java @@ -0,0 +1,103 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.util.Map; + +/** + * @ClassName PluginPayload + * @Description 插件认证完成后,附带到请求服务链路的值 + * @Author vmershen + * @Date 2019/7/8 16:21 + * @Version 1.0 + */ +public class PluginPayload { + + /** + * 附带到请求头的值. + */ + private Map requestHeaders; + + /** + * 附带至响应头的值. + */ + private Map responseHeaders; + + /** + * 附带至请求cookie的值. + */ + private Map requestCookies; + + /** + * 附带至请求参数的值. + */ + private Map parameterMap; + + private String redirectUrl; + + public String getRedirectUrl() { + return redirectUrl; + } + + public void setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + } + + public Map getRequestHeaders() { + return requestHeaders; + } + + public void setRequestHeaders(Map requestHeaders) { + this.requestHeaders = requestHeaders; + } + + public Map getParameterMap() { + return parameterMap; + } + + public void setParameterMap(Map parameterMap) { + this.parameterMap = parameterMap; + } + + public Map getResponseHeaders() { + return responseHeaders; + } + + public void setResponseHeaders(Map responseHeaders) { + this.responseHeaders = responseHeaders; + } + + public Map getRequestCookies() { + return requestCookies; + } + + public void setRequestCookies(Map requestCookies) { + this.requestCookies = requestCookies; + } + + @Override + public String toString() { + return "PluginPayload{" + + "requestHeaders=" + requestHeaders + + ", responseHeaders=" + responseHeaders + + ", requestCookies=" + requestCookies + + ", parameterMap=" + parameterMap + + ", redirectUrl='" + redirectUrl + '\'' + + '}'; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/RequestTransformerPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/RequestTransformerPlugin.java new file mode 100644 index 000000000..51a5f3ed2 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/RequestTransformerPlugin.java @@ -0,0 +1,85 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.type.TypeReference; +import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; + +public class RequestTransformerPlugin extends PluginInfo { + + private static final long serialVersionUID = -2682243185036956532L; + + /** + * 参数配置的JSON串. + */ + private String pluginInfo; + + @JsonIgnore + private RequestTransformerPluginInfo requestTransformerPluginInfo; + + public String getPluginInfo() { + return pluginInfo; + } + + public void setPluginInfo(String pluginInfo) { + this.pluginInfo = pluginInfo; + } + + public RequestTransformerPluginInfo getRequestTransformerPluginInfo() { + return requestTransformerPluginInfo; + } + + public void setRequestTransformerPluginInfo( + RequestTransformerPluginInfo requestTransformerPluginInfo) { + this.requestTransformerPluginInfo = requestTransformerPluginInfo; + } + + @Override + @JsonIgnore + public void check() { + super.check(); + if (StringUtils.isEmpty(pluginInfo)) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "验证插件参数"); + } + + try { + requestTransformerPluginInfo = JacksonUtils.deserialize(pluginInfo, new TypeReference() { }); + + } + catch (Throwable t) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "验证插件格式"); + } + + int sum = 0; + for (TransformerAction action : requestTransformerPluginInfo.getActions()) { + if (action.getWeight() == null || action.getWeight() < 0) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, + "权重值不合法:" + action.getWeight()); + } + sum += action.getWeight(); + } + if (sum > 100) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, + "验证插件权重失败,当前权重总和为" + sum); + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/RequestTransformerPluginInfo.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/RequestTransformerPluginInfo.java new file mode 100644 index 000000000..8f44d776d --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/RequestTransformerPluginInfo.java @@ -0,0 +1,58 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import java.util.List; + + +public class RequestTransformerPluginInfo { + + /** + * 流量匹配条件。当全部流量时,为 null 或空集合. + */ + private List filters; + + /** + * 改写流量行为集合. + */ + private List actions; + + public List getFilters() { + return filters; + } + + public void setFilters(List filters) { + this.filters = filters; + } + + public List getActions() { + return actions; + } + + public void setActions(List actions) { + this.actions = actions; + } + + @Override + public String toString() { + return "RequestTransformerPluginInfo{" + + "filters=" + filters + + ", actions=" + actions + + '}'; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TagPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TagPlugin.java new file mode 100644 index 000000000..4d2deb4e9 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TagPlugin.java @@ -0,0 +1,61 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.tsf.gateway.core.constant.PluginConstants; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; +import com.tencent.tsf.gateway.core.util.PluginUtil; + +/** + * @author seanlxliu + * @since 2019/9/12 + */ +public class TagPlugin extends PluginInfo { + + private static final long serialVersionUID = -2682243185036956532L; + + /** + * 参数配置的JSON串. + */ + private String tagPluginInfoList; + + public String getTagPluginInfoList() { + return tagPluginInfoList; + } + + public void setTagPluginInfoList(String tagPluginInfoList) { + this.tagPluginInfoList = tagPluginInfoList; + } + + @Override + @JsonIgnore + public void check() { + super.check(); + if (StringUtils.isEmpty(tagPluginInfoList)) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "验证Tag插件参数"); + } + + if (StringUtils.length(tagPluginInfoList) > PluginConstants.TAG_PLUGIN_INFO_LIST_LIMIT || + !PluginUtil.predicateJsonFormat(tagPluginInfoList)) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_INVALID, "验证Tag插件参数"); + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TagPluginInfo.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TagPluginInfo.java new file mode 100644 index 000000000..09ef3ee1d --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TagPluginInfo.java @@ -0,0 +1,81 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.tencent.cloud.plugin.gateway.context.Position; + +/** + * @author seanlxliu + * @since 2019/9/12 + */ +public class TagPluginInfo { + + /** + * 参数位置. + */ + private Position tagPosition; + + /** + * 参数名称. + */ + private String preTagName; + + /** + * 转化后标签名称,不填写表示使用原参数名称. + */ + private String postTagName; + + /** + * 是否设置为TraceId:N/Y,默认为N. + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private String traceIdEnabled; + + public Position getTagPosition() { + return tagPosition; + } + + public void setTagPosition(Position tagPosition) { + this.tagPosition = tagPosition; + } + + public String getPreTagName() { + return preTagName; + } + + public void setPreTagName(String preTagName) { + this.preTagName = preTagName; + } + + public String getPostTagName() { + return postTagName; + } + + public void setPostTagName(String postTagName) { + this.postTagName = postTagName; + } + + public String getTraceIdEnabled() { + return traceIdEnabled; + } + + public void setTraceIdEnabled(String traceIdEnabled) { + this.traceIdEnabled = traceIdEnabled; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TransformerAction.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TransformerAction.java new file mode 100644 index 000000000..bec9a623e --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TransformerAction.java @@ -0,0 +1,92 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import com.tencent.cloud.plugin.gateway.context.Position; + +public class TransformerAction { + + /** + * 目前仅支持新增 add,未来可能增加 edit、delete. + */ + private String action; + + private Position tagPosition; + + /** + * 转化后标签名称. + */ + private String tagName; + /** + * 未来可能支持 tagValue 来着动态的. + */ + private String tagValue; + + private Integer weight; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public Position getTagPosition() { + return tagPosition; + } + + public void setTagPosition(Position tagPosition) { + this.tagPosition = tagPosition; + } + + public String getTagName() { + return tagName; + } + + public void setTagName(String tagName) { + this.tagName = tagName; + } + + public String getTagValue() { + return tagValue; + } + + public void setTagValue(String tagValue) { + this.tagValue = tagValue; + } + + public Integer getWeight() { + return weight; + } + + public void setWeight(Integer weight) { + this.weight = weight; + } + + @Override + public String toString() { + return "TransformerAction{" + + "action='" + action + '\'' + + ", tagPosition=" + tagPosition + + ", tagName='" + tagName + '\'' + + ", tagValue='" + tagValue + '\'' + + ", weight=" + weight + + '}'; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TransformerTag.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TransformerTag.java new file mode 100644 index 000000000..24d7652f4 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/model/TransformerTag.java @@ -0,0 +1,41 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.model; + +import com.tencent.cloud.plugin.gateway.context.Position; +import com.tencent.polaris.plugins.connector.consul.service.common.TagCondition; + +public class TransformerTag extends TagCondition { + + private Position tagPosition; + + public Position getTagPosition() { + return tagPosition; + } + + public void setTagPosition(Position tagPosition) { + this.tagPosition = tagPosition; + } + + @Override + public String toString() { + return "TransformerTag{" + + "tagPosition=" + tagPosition + + "} " + super.toString(); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/GatewayPluginFactory.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/GatewayPluginFactory.java new file mode 100644 index 000000000..acb7cc43f --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/GatewayPluginFactory.java @@ -0,0 +1,59 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.plugin; + +import java.lang.invoke.MethodHandles; +import java.util.HashMap; +import java.util.Map; + +import com.tencent.polaris.api.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @ClassName GatewayPluginContext + * @Description TODO + * @Author vmershen + * @Date 2019/7/9 15:44 + * @Version 1.0 + */ +public class GatewayPluginFactory { + + //根据插件type找对应的插件执行类 + private static final Map gatewayPluginExecutorMap = new HashMap<>(); + private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + //获取插件业务执行器 + public static IGatewayPlugin getGatewayPluginExecutor(String type) { + if (StringUtils.isEmpty(type)) { + return null; + } + return gatewayPluginExecutorMap.get(type); + } + + public void putGatewayPlugin(String type, IGatewayPlugin gatewayPlugin) { + gatewayPluginExecutorMap.put(type, gatewayPlugin); + } + + public void close() { + logger.info("gatewayPluginExecutorMap start clear"); + gatewayPluginExecutorMap.clear(); + logger.info("gatewayPluginExecutorMap clear success"); + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/IGatewayPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/IGatewayPlugin.java new file mode 100644 index 000000000..628a30d19 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/IGatewayPlugin.java @@ -0,0 +1,35 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.plugin; + +import com.tencent.tsf.gateway.core.TsfGatewayRequest; +import com.tencent.tsf.gateway.core.model.PluginInfo; +import com.tencent.tsf.gateway.core.model.PluginPayload; + +/** + * @author vmershen + * 2019/7/7 17:08 + */ +public interface IGatewayPlugin { + + default PluginPayload invoke(PluginInfo info, TsfGatewayRequest tsfGatewayRequest) { + return startUp((T) info, tsfGatewayRequest); + } + + PluginPayload startUp(T pluginInfo, TsfGatewayRequest tsfGatewayRequest); +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/JwtGatewayPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/JwtGatewayPlugin.java new file mode 100644 index 000000000..9117aa817 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/JwtGatewayPlugin.java @@ -0,0 +1,174 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.plugin; + +import java.lang.invoke.MethodHandles; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.plugin.gateway.context.Position; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.tsf.gateway.core.TsfGatewayRequest; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; +import com.tencent.tsf.gateway.core.model.ClaimMapping; +import com.tencent.tsf.gateway.core.model.JwtPlugin; +import com.tencent.tsf.gateway.core.model.PluginPayload; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import shade.polaris.org.jose4j.jwa.AlgorithmConstraints; +import shade.polaris.org.jose4j.jwk.PublicJsonWebKey; +import shade.polaris.org.jose4j.jwt.JwtClaims; +import shade.polaris.org.jose4j.jwt.MalformedClaimException; +import shade.polaris.org.jose4j.jwt.consumer.InvalidJwtException; +import shade.polaris.org.jose4j.jwt.consumer.JwtConsumer; +import shade.polaris.org.jose4j.jwt.consumer.JwtConsumerBuilder; +import shade.polaris.org.jose4j.lang.JoseException; + + +/** + * @ClassName JwtGatewayPlugin + * @Description TODO + * @Author vmershen + * @Date 2019/7/8 16:45 + * @Version 1.0 + */ +public class JwtGatewayPlugin implements IGatewayPlugin { + + private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + @Override + public PluginPayload startUp(JwtPlugin pluginInfo, TsfGatewayRequest tsfGatewayRequest) { + Map parameterMap = tsfGatewayRequest.getParameterMap(); + String tokenBaggagePosition = pluginInfo.getTokenBaggagePosition(); + if (Position.fromString(tokenBaggagePosition) == null) { + logger.error("tokenBaggagePosition is wrong, tokenBaggagePosition: {}, tsfGatewayRequest: {}", tokenBaggagePosition, tsfGatewayRequest); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "tokenBaggagePosition is wrong"); + } + String idToken; + if (Position.HEADER.equals(Position.fromString(tokenBaggagePosition))) { + idToken = tsfGatewayRequest.getRequestHeader(pluginInfo.getTokenKeyName()); + } + else { + //queryParam中取 + if (parameterMap.get(pluginInfo.getTokenKeyName()) == null || parameterMap.get(pluginInfo.getTokenKeyName()).length == 0) { + logger.error("idToken is empty, tsfGatewayRequest: {}", tsfGatewayRequest); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "idToken is empty"); + } + idToken = parameterMap.get(pluginInfo.getTokenKeyName())[0]; + } + if (StringUtils.isEmpty(idToken)) { + logger.error("idToken is empty, tsfGatewayRequest: {}", tsfGatewayRequest); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "idToken is empty"); + } + + //获取公钥 + String publicKeyJson = pluginInfo.getPublicKeyJson(); + PublicJsonWebKey jwk; + //验签 + try { + jwk = PublicJsonWebKey.Factory.newPublicJwk(publicKeyJson); + jwk.setKeyId(pluginInfo.getKid()); + } + catch (JoseException e) { + logger.error("Generate PublicKey Error", e); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "Generate PublicKey Error"); + } + //解析idToken, 验签 + JwtConsumer jwtConsumer = new JwtConsumerBuilder() + .setRequireExpirationTime() // the JWT must have an expiration time + .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew + .setRequireSubject() // the JWT must have a subject claim + .setVerificationKey(jwk.getPublicKey()) + .setJwsAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST, jwk.getAlgorithm())) + // ignore audience + .setSkipDefaultAudienceValidation() + .build(); // create the JwtConsumer instance + + JwtClaims jwtClaims; + try { + jwtClaims = jwtConsumer.processToClaims(idToken); + } + catch (InvalidJwtException e) { + // Whether or not the JWT has expired being one common reason for invalidity + if (e.hasExpired()) { + try { + long expirationTime = e.getJwtContext() + .getJwtClaims() + .getExpirationTime() + .getValueInMillis(); + String msg = String + .format("JWT expired at (%d)", expirationTime); + logger.error(msg); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, msg); + } + catch (MalformedClaimException e1) { + // ignore + } + } + + logger.warn("Invalid JWT! tsfGatewayRequest:{}, error:{}", tsfGatewayRequest, e.getMessage()); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "Invalid JWT"); + } + + if (jwtClaims == null) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "Invalid JWT"); + } + + PluginPayload pluginPayload = new PluginPayload(); + //若成功,则封装claim字段映射到后台服务 + if (StringUtils.isNotEmpty(pluginInfo.getClaimMappingJson())) { + logger.info("claim json is : " + pluginInfo.getClaimMappingJson()); + List claimMappings; + try { + claimMappings = JacksonUtils.deserializeCollection(pluginInfo.getClaimMappingJson(), ClaimMapping.class); + } + catch (Throwable t) { + logger.error("[tsf-gateway] claimMappingJson deserialize data to claimMappings occur exception.", t); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_SERIALIZE_ERROR, + "claimMappingJson deserialize data to claimMappings occur exception"); + } + + Map paramsMap = new HashMap<>(); + Map headerParamsMap = new HashMap<>(); + for (ClaimMapping claimMapping : claimMappings) { + try { + String claimValue = jwtClaims.getStringClaimValue(claimMapping.getParameterName()); + if (!StringUtils.isEmpty(claimValue)) { + if (Position.HEADER.equals(Position.fromString(claimMapping.getLocation()))) { + headerParamsMap.put(claimMapping.getMappingParameterName(), claimValue); + } + else if (Position.QUERY.equals(Position.fromString(claimMapping.getLocation()))) { + paramsMap.put(claimMapping.getMappingParameterName(), new String[] {claimValue}); + } + } + } + catch (MalformedClaimException e) { + logger.warn("Claim mapping error, parameterName: " + claimMapping.getParameterName(), e); + } + } + pluginPayload.setRequestHeaders(headerParamsMap); + pluginPayload.setParameterMap(paramsMap); + } + + return pluginPayload; + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/OAuthGatewayPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/OAuthGatewayPlugin.java new file mode 100644 index 000000000..14b2694cb --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/OAuthGatewayPlugin.java @@ -0,0 +1,304 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.plugin; + +import java.lang.invoke.MethodHandles; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.common.constant.MetadataConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.plugin.gateway.context.Position; +import com.tencent.cloud.polaris.context.PolarisSDKContextManager; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.polaris.assembly.api.AssemblyAPI; +import com.tencent.polaris.assembly.api.pojo.TraceAttributes; +import com.tencent.tsf.gateway.core.TsfGatewayRequest; +import com.tencent.tsf.gateway.core.constant.HttpMethod; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; +import com.tencent.tsf.gateway.core.http.HttpConnectionPoolUtil; +import com.tencent.tsf.gateway.core.model.OAuthPlugin; +import com.tencent.tsf.gateway.core.model.OAuthResult; +import com.tencent.tsf.gateway.core.model.PluginPayload; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.DefaultRequest; +import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; +import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; + +import static com.tencent.tsf.gateway.core.constant.GatewayConstant.GATEWAY_WILDCARD_SERVICE_NAME; + +public class OAuthGatewayPlugin implements IGatewayPlugin { + + private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private final LoadBalancerClientFactory clientFactory; + @Value("${tsf.gateway.plugin.oauth.authorization:true}") + Boolean openAuthorization; + private final PolarisSDKContextManager polarisSDKContextManager; + + public OAuthGatewayPlugin(LoadBalancerClientFactory clientFactory, PolarisSDKContextManager polarisSDKContextManager) { + this.clientFactory = clientFactory; + this.polarisSDKContextManager = polarisSDKContextManager; + } + + @Override + public PluginPayload startUp(OAuthPlugin pluginInfo, TsfGatewayRequest tsfGatewayRequest) { + //OAuth认证逻辑 + //构建http请求,访问第三方认证服务器 + Map parameterMap = tsfGatewayRequest.getParameterMap(); + Map paramsMap = new HashMap<>(); + Map headerParamsMap = new HashMap<>(); + String tokenAuthUrl = pluginInfo.getTokenAuthUrl(); + //认证结果 + String authResult = null; + //获取token携带位置 + String tokenBaggagePosition = pluginInfo.getTokenBaggagePosition(); + if (Position.fromString(tokenBaggagePosition) == null) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "tokenBaggagePosition is wrong"); + } + if (Position.HEADER.equals(Position.fromString(tokenBaggagePosition))) { + logger.info("header is : {} = {} ", pluginInfo.getTokenKeyName(), tsfGatewayRequest.getRequestHeader( + pluginInfo.getTokenKeyName())); + if (StringUtils.isEmpty(tsfGatewayRequest.getRequestHeader(pluginInfo.getTokenKeyName()))) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "header token value is empty"); + } + headerParamsMap.put(pluginInfo.getTokenKeyName(), tsfGatewayRequest.getRequestHeader(pluginInfo.getTokenKeyName())); + } + else { + //queryParam中取 + if (parameterMap.get(pluginInfo.getTokenKeyName()) == null || parameterMap.get(pluginInfo.getTokenKeyName()).length == 0) { + logger.info("parameterMap is empty"); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "parameter token value is empty"); + } + paramsMap.put(pluginInfo.getTokenKeyName(), parameterMap.get(pluginInfo.getTokenKeyName())[0]); + } + + // 添加Oauth Authorization授权 + String headerAuthorization = tsfGatewayRequest.getRequestHeader("Authorization"); + if (openAuthorization && StringUtils.isNotBlank(headerAuthorization)) { + logger.info("[tsf-gateway] OAuthGatewayPlugin openAuthorization is true, put headerAuthorization:{}", headerAuthorization); + headerParamsMap.put("authorization", headerAuthorization); + } + + //构建认证请求方法 + String tokenAuthMethod = pluginInfo.getTokenAuthMethod(); + if (HttpMethod.getHttpMethod(tokenAuthMethod) == null) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "tokenAuthMethod is wrong"); + } + + Integer timeout = pluginInfo.getExpireTime() != null ? 1000 * pluginInfo.getExpireTime() : null; + + if (StringUtils.isNotBlank(pluginInfo.getTokenAuthServiceName())) { + // 认证地址选择的是微服务时 + authResult = tokenAuthByMicroservice(pluginInfo, tsfGatewayRequest, paramsMap, headerParamsMap, tokenAuthUrl, tokenAuthMethod, timeout); + } + else { + // 认证地址选择的是外部地址时 + authResult = sendAuthRequestByHttpMethod(paramsMap, headerParamsMap, tokenAuthUrl, tokenAuthMethod, timeout); + } + + OAuthResult oAuthResult = null; + try { + //反序列化authResult为OAuthResult + //若反序列化失败,则认为用户的认证服务器未遵守tsf微服务网关OAuth插件的使用协议,导致认证失败。 + oAuthResult = JacksonUtils.deserialize(authResult, OAuthResult.class); + } + catch (Throwable t) { + logger.error("[tsf-gateway] authResult deserialize data to OAuthResult occur exception.", t); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_SERIALIZE_ERROR, "oAuthResult deserialize occur error"); + } + + PluginPayload pluginPayload = new PluginPayload(); + if (!oAuthResult.getResult()) { + //若认证失败,判断是否跳转到用户重定向页面 + if (StringUtils.isNotEmpty(pluginInfo.getRedirectUrl())) { + pluginPayload.setRedirectUrl(pluginInfo.getRedirectUrl()); + } + else { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "Token Auth failed"); + } + + } + + if (StringUtils.isNotBlank(pluginInfo.getPayloadMappingName())) { + //若成功,则封装payload字段映射到后台服务 + if (Position.fromString(pluginInfo.getPayloadMappingPosition()) == null) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "payloadMappingPosition is wrong"); + } + + if (Position.HEADER.equals(Position.fromString(pluginInfo.getPayloadMappingPosition()))) { + Map headerMap = new HashMap<>(); + headerMap.put(pluginInfo.getPayloadMappingName(), oAuthResult.getPayload()); + pluginPayload.setRequestHeaders(headerMap); + } + else if (Position.QUERY.equals(Position.fromString(pluginInfo.getPayloadMappingPosition()))) { + //queryParam中取 + Map queryMap = new HashMap<>(); + queryMap.put(pluginInfo.getPayloadMappingName(), new String[] {oAuthResult.getPayload()}); + pluginPayload.setParameterMap(queryMap); + } + } + + return pluginPayload; + + } + + private String tokenAuthByMicroservice(OAuthPlugin pluginInfo, TsfGatewayRequest tsfGatewayRequest, + Map paramsMap, Map headerParamsMap, + String tokenAuthUrl, String tokenAuthMethod, Integer timeout) { + + String serviceName = pluginInfo.getTokenAuthServiceName(); + String namespace = null; + String backupNamespace = MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_APPLICATION_NONE, + MetadataConstant.POLARIS_TARGET_NAMESPACE); + if (serviceName.contains("/")) { + String[] parts = serviceName.split("/"); + namespace = parts[0]; + serviceName = parts[1]; + } + try { + MetadataContextHolder.get().putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE, + MetadataConstant.POLARIS_TARGET_NAMESPACE, namespace); + ReactorLoadBalancer loadBalancer = this.clientFactory.getInstance(serviceName, + ReactorServiceInstanceLoadBalancer.class); + + Response instance = loadBalancer.choose(new DefaultRequest<>()).toFuture().get(); + URI uri = tsfGatewayRequest.getUri(); + if (logger.isDebugEnabled()) { + logger.debug("[tokenAuthByMicroservice] plugin:{}, tsfGatewayRequest:{}", pluginInfo, tsfGatewayRequest); + } + if (instance.getServer() == null) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_REQUEST_NOT_FOUND, "Unable to find instance for " + pluginInfo.getTokenAuthServiceName()); + } + + fillTracingContext(namespace, serviceName); + + String newRequestUrl = uri.getScheme() + "://" + GATEWAY_WILDCARD_SERVICE_NAME + tokenAuthUrl; + URI newUri = new URI(newRequestUrl); + // 这个requestUrl是从注册中心拿到服务列表并且balance之后带有IP信息的URL + // http://127.0.0.1:8080/group1/namespace1/Consumer-demo/echo-rest/1?user=1 + URI requestUrl = this.reconstructURI(new OauthDelegatingServiceInstance(instance.getServer()), newUri); + logger.debug("LoadBalancerClientFilter url chosen: " + requestUrl); + return sendAuthRequestByHttpMethod(paramsMap, headerParamsMap, requestUrl.toASCIIString(), tokenAuthMethod, timeout); + } + catch (Exception e) { + logger.error("MicroService {} Request Auth Server Error", tokenAuthMethod, e); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "MicroService {} Request Auth Server Error", tokenAuthMethod); + } + finally { + // restore context + MetadataContextHolder.get().putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE, + MetadataConstant.POLARIS_TARGET_NAMESPACE, backupNamespace); + } + } + + protected URI reconstructURI(ServiceInstance serviceInstance, URI original) { + return LoadBalancerUriTools.reconstructURI(serviceInstance, original); + } + + private String sendAuthRequestByHttpMethod(Map paramsMap, Map headerParamsMap, + String tokenAuthUrl, String tokenAuthMethod, Integer timeout) { + if (HttpMethod.GET.equals(HttpMethod.getHttpMethod(tokenAuthMethod))) { + try { + return HttpConnectionPoolUtil.httpGet(tokenAuthUrl, paramsMap, headerParamsMap, timeout); + } + catch (Throwable e) { + logger.error("GET Request Auth Server Error", e); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "DirectAddress GET Request Auth Server Error"); + } + } + else { + //POST请求 + try { + return HttpConnectionPoolUtil.httpPostWithJSON(tokenAuthUrl, paramsMap, null, headerParamsMap, timeout); + } + catch (Throwable e) { + logger.error("GET Request Auth Server Error", e); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "DirectAddress POST Request Auth Server Error"); + } + } + } + + void fillTracingContext(String namespace, String serviceName) { + + Map attributes = new HashMap<>(); + attributes.put("net.peer.service", serviceName); + attributes.put("remote.namespace-id", namespace); + + TraceAttributes traceAttributes = new TraceAttributes(); + traceAttributes.setAttributes(attributes); + traceAttributes.setAttributeLocation(TraceAttributes.AttributeLocation.BAGGAGE); + + AssemblyAPI assemblyAPI = polarisSDKContextManager.getAssemblyAPI(); + assemblyAPI.updateTraceAttributes(traceAttributes); + } + + class OauthDelegatingServiceInstance implements ServiceInstance { + final ServiceInstance delegate; + + OauthDelegatingServiceInstance(ServiceInstance delegate) { + this.delegate = delegate; + } + + @Override + public String getServiceId() { + return delegate.getServiceId(); + } + + @Override + public String getHost() { + return delegate.getHost(); + } + + @Override + public int getPort() { + return delegate.getPort(); + } + + @Override + public boolean isSecure() { + return delegate.isSecure(); + } + + @Override + public URI getUri() { + return delegate.getUri(); + } + + @Override + public Map getMetadata() { + return delegate.getMetadata(); + } + + @Override + public String getScheme() { + return delegate.getScheme(); + } + + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/ReqTransformerGatewayPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/ReqTransformerGatewayPlugin.java new file mode 100644 index 000000000..919e14704 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/ReqTransformerGatewayPlugin.java @@ -0,0 +1,40 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.plugin; + +import java.lang.invoke.MethodHandles; + +import com.tencent.tsf.gateway.core.TsfGatewayRequest; +import com.tencent.tsf.gateway.core.model.PluginPayload; +import com.tencent.tsf.gateway.core.model.RequestTransformerPlugin; +import com.tencent.tsf.gateway.core.util.PluginUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class ReqTransformerGatewayPlugin implements IGatewayPlugin { + + private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + @Override + public PluginPayload startUp(RequestTransformerPlugin plugin, TsfGatewayRequest tsfGatewayRequest) { + PluginPayload payload = new PluginPayload(); + return PluginUtil.doRequestTransformer(plugin.getRequestTransformerPluginInfo(), tsfGatewayRequest, payload); + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/TagGatewayPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/TagGatewayPlugin.java new file mode 100644 index 000000000..cb1d75854 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/plugin/TagGatewayPlugin.java @@ -0,0 +1,46 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.plugin; + +import java.lang.invoke.MethodHandles; + +import com.tencent.tsf.gateway.core.TsfGatewayRequest; +import com.tencent.tsf.gateway.core.model.PluginPayload; +import com.tencent.tsf.gateway.core.model.TagPlugin; +import com.tencent.tsf.gateway.core.util.PluginUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author seanlxliu + * @since 2019/9/12 + */ +public class TagGatewayPlugin implements IGatewayPlugin { + + private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public TagGatewayPlugin() { + } + + @Override + public PluginPayload startUp(TagPlugin tagPlugin, TsfGatewayRequest tsfGatewayRequest) { + PluginPayload payload = new PluginPayload(); + return PluginUtil.transferToTag(tagPlugin.getTagPluginInfoList(), tsfGatewayRequest, payload); + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/CookieUtil.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/CookieUtil.java new file mode 100644 index 000000000..92541c966 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/CookieUtil.java @@ -0,0 +1,47 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.util; + +import java.util.Map; + +import org.springframework.util.CollectionUtils; + +/** + * @author: vmershen + * @description: + * @create: 2020-05-23 17:41 + **/ +public final class CookieUtil { + + private CookieUtil() { + + } + + public static void buildCookie(StringBuilder cookieStringBuilder, Map requestCookieMap) { + if (!CollectionUtils.isEmpty(requestCookieMap)) { + for (Map.Entry cookieEntry : requestCookieMap.entrySet()) { + if (cookieStringBuilder.length() == 0) { + cookieStringBuilder.append(cookieEntry.getKey() + "=" + cookieEntry.getValue()); + continue; + } + cookieStringBuilder.append("; "); + cookieStringBuilder.append(cookieEntry.getKey() + "=" + cookieEntry.getValue()); + } + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/IdGenerator.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/IdGenerator.java new file mode 100644 index 000000000..95d33d3c2 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/IdGenerator.java @@ -0,0 +1,95 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.util; + +import java.util.UUID; + +import shade.polaris.org.apache.commons.codec.binary.Base64; + +/** + * @author kysonli + * 2019/2/26 16:20 + */ +public final class IdGenerator { + + private IdGenerator() { + } + + public static String uuid() { + UUID uuid = UUID.randomUUID(); + return uuid.toString(); + } + + public static String generate() { + UUID uuid = UUID.randomUUID(); + return compressedUUID(uuid); + } + + public static String generateId(String prefix) { + return generateId(prefix, '#'); + } + + public static String generateId(String prefix, char splitor) { + String uuid = generate(); + return prefix + splitor + uuid; + } + + private static String compressedUUID(UUID uuid) { + byte[] byUuid = new byte[16]; + long least = uuid.getLeastSignificantBits(); + long most = uuid.getMostSignificantBits(); + long2bytes(most, byUuid, 0); + long2bytes(least, byUuid, 8); + return Base64.encodeBase64URLSafeString(byUuid); + } + + private static void long2bytes(long value, byte[] bytes, int offset) { + for (int i = 7; i > -1; --i) { + bytes[offset++] = (byte) ((int) (value >> 8 * i & 255L)); + } + + } + + private static String compress(String uuidString) { + UUID uuid = UUID.fromString(uuidString); + return compressedUUID(uuid); + } + + private static String uncompress(String compressedUuid) { + if (compressedUuid.length() != 22) { + throw new IllegalArgumentException("Invalid uuid!"); + } + else { + byte[] byUuid = Base64.decodeBase64(compressedUuid + "=="); + long most = bytes2long(byUuid, 0); + long least = bytes2long(byUuid, 8); + UUID uuid = new UUID(most, least); + return uuid.toString(); + } + } + + private static long bytes2long(byte[] bytes, int offset) { + long value = 0L; + + for (int i = 7; i > -1; --i) { + value |= ((long) bytes[offset++] & 255L) << 8 * i; + } + + return value; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/PluginUtil.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/PluginUtil.java new file mode 100644 index 000000000..2798cf92d --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/PluginUtil.java @@ -0,0 +1,339 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.util; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.plugin.gateway.context.Position; +import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.api.utils.RuleUtils; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.polaris.plugins.connector.consul.service.common.TagCondition; +import com.tencent.polaris.plugins.connector.consul.service.common.TagConditionUtil; +import com.tencent.polaris.specification.api.v1.model.ModelProto; +import com.tencent.tsf.gateway.core.TsfGatewayRequest; +import com.tencent.tsf.gateway.core.constant.PluginConstants; +import com.tencent.tsf.gateway.core.exception.TsfGatewayError; +import com.tencent.tsf.gateway.core.exception.TsfGatewayException; +import com.tencent.tsf.gateway.core.model.PluginDetail; +import com.tencent.tsf.gateway.core.model.PluginInfo; +import com.tencent.tsf.gateway.core.model.PluginPayload; +import com.tencent.tsf.gateway.core.model.RequestTransformerPluginInfo; +import com.tencent.tsf.gateway.core.model.TagPluginInfo; +import com.tencent.tsf.gateway.core.model.TransformerAction; +import com.tencent.tsf.gateway.core.model.TransformerTag; +import io.opentelemetry.api.trace.Span; +import org.apache.commons.collections.MapUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import shade.polaris.com.google.common.base.Joiner; + +import org.springframework.tsf.core.TsfContext; +import org.springframework.tsf.core.entity.Tag; +import org.springframework.util.AntPathMatcher; + +public final class PluginUtil { + + private static final Logger logger = LoggerFactory.getLogger(PluginUtil.class); + + private static final AntPathMatcher antPathMatcher = new AntPathMatcher(); + + private PluginUtil() { + + } + + public static List sortPluginDetail(Stream pluginDetailStream) { + return pluginDetailStream.distinct().sorted(Comparator.comparing(PluginInfo::getOrder)).collect( + Collectors.toList()); + } + + public static List deserializeTagPluginInfoList(String tagPluginInfoListJson) { + if (StringUtils.length(tagPluginInfoListJson) == 0) { + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, tagPluginInfoListJson); + } + List tagPluginInfoList = null; + try { + tagPluginInfoList = JacksonUtils.deserialize(tagPluginInfoListJson, new TypeReference>() { }); + } + catch (Throwable t) { + logger.error("deserialize tagPluginInfoList : {} occur exception : {}", tagPluginInfoListJson, t); + throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_INVALID, tagPluginInfoListJson); + } + return tagPluginInfoList; + } + + public static PluginPayload transferToTag(String tagPluginListJson, TsfGatewayRequest tsfGatewayRequest, + PluginPayload payload) { + List tagPluginInfoList = PluginUtil.deserializeTagPluginInfoList(tagPluginListJson); + if (CollectionUtils.isEmpty(tagPluginInfoList)) { + return payload; + } + Map responseHeader = new HashMap<>(); + Map tagMap = new HashMap<>(tagPluginInfoList.size()); + + for (TagPluginInfo tagPluginInfo : tagPluginInfoList) { + Position tagPosition = tagPluginInfo.getTagPosition(); + String preTagName = tagPluginInfo.getPreTagName(); + String postTagName = tagPluginInfo.getPostTagName(); + String traceIdEnabled = tagPluginInfo.getTraceIdEnabled(); + switch (tagPosition) { + case QUERY: + Map parameterMap = tsfGatewayRequest.getParameterMap(); + String[] queryTags = parameterMap.get(preTagName); + if (queryTags != null) { + if (StringUtils.isBlank(postTagName)) { + postTagName = preTagName; + } + String tagValue = String.join(",", queryTags); + tagMap.put(postTagName, tagValue); + relateTraceId(responseHeader, postTagName, traceIdEnabled, tagValue); + } + break; + case COOKIE: + String cookieTag = tsfGatewayRequest.getCookie(preTagName); + if (cookieTag != null) { + if (StringUtils.isBlank(postTagName)) { + postTagName = preTagName; + } + tagMap.put(postTagName, cookieTag); + relateTraceId(responseHeader, postTagName, traceIdEnabled, cookieTag); + } + break; + case HEADER: + String headerTag = tsfGatewayRequest.getRequestHeader(preTagName); + if (headerTag != null) { + if (StringUtils.isBlank(postTagName)) { + postTagName = preTagName; + } + tagMap.put(postTagName, headerTag); + relateTraceId(responseHeader, postTagName, traceIdEnabled, headerTag); + } + break; + case PATH: + String path = tsfGatewayRequest.getUri().getPath(); + if (StringUtils.isNotBlank(path)) { + // ensure path start with '/' + if (path.charAt(0) != '/') { + path = "/" + path; + } + + String apiPath = StringUtils.substring(path, StringUtils.ordinalIndexOf(path, "/", 4)); + boolean matched = antPathMatcher.match(preTagName, apiPath); + if (matched) { + Map pathTagMap = antPathMatcher.extractUriTemplateVariables(preTagName, apiPath); + if (CollectionUtils.isNotEmpty(pathTagMap)) { + for (Map.Entry entry : pathTagMap.entrySet()) { + String pathTag = entry.getValue(); + // take the first one + if (pathTag != null) { + postTagName = StringUtils.isBlank(postTagName) ? entry.getKey() : postTagName; + + tagMap.put(postTagName, pathTag); + relateTraceId(responseHeader, postTagName, traceIdEnabled, pathTag); + break; + } + } + } + } + } + break; + default: + break; + } + } + + TsfContext.putTags(tagMap, Tag.ControlFlag.TRANSITIVE); + + if (!responseHeader.isEmpty()) { + Map originalResponseHeaders = payload.getResponseHeaders(); + if (originalResponseHeaders == null) { + payload.setResponseHeaders(responseHeader); + } + else { + originalResponseHeaders.putAll(responseHeader); + } + } + return payload; + } + + private static void relateTraceId(Map responseHeader, String postTagName, String traceIdEnabled, + String tagValue) { + if (PluginConstants.TraceIdEnabledType.Y.equals(PluginConstants.TraceIdEnabledType + .getTraceIdEnabledType(traceIdEnabled))) { + String traceId = Span.current().getSpanContext().getTraceId(); + responseHeader.put(postTagName, tagValue); + responseHeader.put("X-Tsf-TraceId", traceId); + logger.info("TraceId is {} , which is related to tag {}:{}", traceId, postTagName, + tagValue); + } + } + + /** + * 断言Tag插件参数列表. + * @param tagPluginInfoListJson Tag插件参数列表Json串 + * @return 是否符合格式 + */ + public static boolean predicateJsonFormat(String tagPluginInfoListJson) { + if (StringUtils.length(tagPluginInfoListJson) == 0) { + return false; + } + List tagPluginInfos = null; + try { + tagPluginInfos = JacksonUtils.deserialize(tagPluginInfoListJson, + new TypeReference>() { }); + } + catch (Throwable t) { + logger.error("deserialize tagPluginInfoList : {} occur exception : ", tagPluginInfoListJson, t); + return false; + } + tagPluginInfos.forEach(tagPluginInfo -> { + Optional.ofNullable(tagPluginInfo.getTraceIdEnabled()) + .ifPresent(PluginConstants.TraceIdEnabledType::checkValidity); + }); + + return true; + } + + public static PluginPayload doRequestTransformer( + RequestTransformerPluginInfo requestTransformerPluginInfo, + TsfGatewayRequest tsfGatewayRequest, + PluginPayload payload) { + + if (requestTransformerPluginInfo == null || + org.apache.commons.collections.CollectionUtils.isEmpty(requestTransformerPluginInfo.getActions())) { + return payload; + } + + Map requestHeader = new HashMap<>(); + Map tagMap = new HashMap<>(requestTransformerPluginInfo.getActions().size()); + + boolean reqMatch = true; + if (!org.apache.commons.collections.CollectionUtils.isEmpty(requestTransformerPluginInfo.getFilters())) { + for (TransformerTag transformerTag : requestTransformerPluginInfo.getFilters()) { + // 多个条件时是与关系,有一个为 false 时直接退出 + if (!reqMatch) { + break; + } + switch (transformerTag.getTagPosition()) { + case QUERY: + Map parameterMap = tsfGatewayRequest.getParameterMap(); + String[] queryTags = parameterMap.get(transformerTag.getTagField()); + String queryValue = null; + if (queryTags != null) { + queryValue = Joiner.on(",").join(queryTags); + } + reqMatch &= matchTag(transformerTag, queryValue); + break; + case COOKIE: + String cookieTag = tsfGatewayRequest.getCookie(transformerTag.getTagField()); + reqMatch &= matchTag(transformerTag, cookieTag); + break; + case HEADER: + String headerTag = tsfGatewayRequest.getRequestHeader(transformerTag.getTagField()); + reqMatch &= matchTag(transformerTag, headerTag); + break; + case PATH: + String path = tsfGatewayRequest.getUri().getPath(); + if (StringUtils.isNotBlank(path)) { + // ensure path start with '/' + if (path.charAt(0) != '/') { + path = "/" + path; + } + + String apiPath = StringUtils.substring(path, StringUtils.ordinalIndexOf(path, "/", 4)); + boolean matched = antPathMatcher.match(transformerTag.getTagField(), apiPath); + if (!matched) { + logger.debug("[doRequestTransformer] path pattern not match, field:{}, apiPath:{}", transformerTag.getTagField(), apiPath); + reqMatch = false; + break; + } + Map pathTagMap = antPathMatcher.extractUriTemplateVariables(transformerTag.getTagField(), apiPath); + if (MapUtils.isNotEmpty(pathTagMap)) { + for (Map.Entry entry : pathTagMap.entrySet()) { + String pathTag = entry.getValue(); + // take the first one + reqMatch &= matchTag(transformerTag, pathTag); + break; + } + } + } + break; + default: + break; + + } + } + } + + if (reqMatch) { + // 每次请求只会进入这里一次,不需要像 RequestRouteDestRandomValueUtil 那样弄个 thread local 变量 + // 规则权重总和可能小于 100,但按 100 来随机 + int random = (int) (Math.random() * 100); + int current = 0; + for (TransformerAction action : requestTransformerPluginInfo.getActions()) { + current += action.getWeight(); + // match + if (random < current) { + switch (action.getTagPosition()) { + case TSF_TAG: + tagMap.put(action.getTagName(), action.getTagValue()); + break; + case HEADER: + requestHeader.put(action.getTagName(), action.getTagValue()); + break; + default: + break; + } + // 命中权重,退出 + break; + } + } + } + if (logger.isDebugEnabled()) { + logger.debug("[doRequestTransformer] put tags:{}, reqMatch:{}", tagMap, reqMatch); + } + TsfContext.putTags(tagMap, Tag.ControlFlag.TRANSITIVE); + + if (!requestHeader.isEmpty()) { + // request header 是网关向后请求时携带的,response header 是后端业务返回经过网关时携带的 + Map originalRequestHeaders = payload.getRequestHeaders(); + if (originalRequestHeaders == null) { + payload.setRequestHeaders(requestHeader); + } + else { + originalRequestHeaders.putAll(requestHeader); + } + } + return payload; + } + + public static boolean matchTag(TagCondition transformerTag, String targetTagValue) { + ModelProto.MatchString.MatchStringType matchType = TagConditionUtil.parseMatchStringType(transformerTag); + + return RuleUtils.matchStringValue(matchType, targetTagValue, transformerTag.getTagValue()); + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/TsfSignUtil.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/TsfSignUtil.java new file mode 100644 index 000000000..bd2a93e88 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/core/util/TsfSignUtil.java @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.core.util; + +import com.tencent.tsf.gateway.core.constant.TsfAlgType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import shade.polaris.org.apache.commons.codec.binary.Base64; +import shade.polaris.org.apache.commons.codec.digest.HmacUtils; + + +/** + * @author kysonli + * 2019/2/26 16:06 + */ +public final class TsfSignUtil { + private static final Logger logger = LoggerFactory.getLogger(TsfSignUtil.class); + + + private TsfSignUtil() { + } + + /** + * 生成签名. + * + * @param nonce 随机字符串 + * @param secretId 秘钥ID + * @param secretKey 秘钥值 + * @param algType 签名算法 {@link TsfAlgType} + */ + @SuppressWarnings("deprecation") + public static String generate(String nonce, String secretId, String secretKey, TsfAlgType algType) { + String digestValue = nonce + secretId + secretKey; + byte[] serverSignBytes; + switch (algType) { + case HMAC_MD5: + serverSignBytes = HmacUtils.hmacMd5(secretKey, digestValue); + break; + case HMAC_SHA_1: + serverSignBytes = HmacUtils.hmacSha1(secretKey, digestValue); + break; + case HMAC_SHA_256: + serverSignBytes = HmacUtils.hmacSha256(secretKey, digestValue); + break; + case HMAC_SHA_512: + serverSignBytes = HmacUtils.hmacSha512(secretKey, digestValue); + break; + default: + throw new UnsupportedOperationException("不支持的鉴权算法: " + algType); + } + String signValue = Base64.encodeBase64String(serverSignBytes); + if (logger.isDebugEnabled()) { + logger.debug("签名明文:{},签名密文:{}", digestValue, signValue); + } + return signValue; + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/scg/AbstractTsfGlobalFilter.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/scg/AbstractTsfGlobalFilter.java new file mode 100644 index 000000000..c6bf9f3cd --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/tsf/gateway/scg/AbstractTsfGlobalFilter.java @@ -0,0 +1,50 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.tsf.gateway.scg; + +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.web.server.ServerWebExchange; + +/** + * Compatible with old versions TSF SDK. + * + * @author Shedfree Wu + */ +public abstract class AbstractTsfGlobalFilter implements GlobalFilter, Ordered { + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + if (shouldFilter(exchange, chain)) { + return doFilter(exchange, chain); + } + else { + return chain.filter(exchange); + } + } + + @Override + abstract public int getOrder(); + + abstract public boolean shouldFilter(ServerWebExchange exchange, GatewayFilterChain chain); + + abstract public Mono doFilter(ServerWebExchange exchange, GatewayFilterChain chain); +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/GatewayPluginAutoConfigurationTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/GatewayPluginAutoConfigurationTest.java index 0320673bc..45c6ec6d5 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/GatewayPluginAutoConfigurationTest.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/GatewayPluginAutoConfigurationTest.java @@ -35,6 +35,7 @@ import org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAut import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.gateway.config.GatewayAutoConfiguration; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -139,5 +140,10 @@ class GatewayPluginAutoConfigurationTest { PolarisConfigProperties polarisConfigProperties() { return new PolarisConfigProperties(); } + + @Bean + LoadBalancerClientFactory loadBalancerClientFactory() { + return mock(LoadBalancerClientFactory.class); + } } } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest.java index 0e4e3b8fb..0e4d0529c 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest.java @@ -17,7 +17,6 @@ package com.tencent.cloud.plugin.gateway; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -28,6 +27,7 @@ import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.ApplicationContext; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -62,7 +62,7 @@ public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest { @Test void testGetOrder() { int order = processor.getOrder(); - Assertions.assertEquals(PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.ORDER, order); + assertThat(order).isEqualTo(PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.ORDER); } @Test @@ -75,7 +75,7 @@ public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest { Object result = processor.postProcessAfterInitialization(originalInterceptor, beanName); // Assert - Assertions.assertInstanceOf(PolarisReactiveLoadBalancerClientFilter.class, result); + assertThat(result).isInstanceOf(PolarisReactiveLoadBalancerClientFilter.class); } @Test @@ -88,7 +88,7 @@ public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest { Object result = processor.postProcessAfterInitialization(originalBean, beanName); // Assert - Assertions.assertSame(originalBean, result); + assertThat(result).isSameAs(originalBean); } } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterTest.java index e68db2ceb..652a61d7c 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterTest.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterTest.java @@ -20,7 +20,6 @@ package com.tencent.cloud.plugin.gateway; import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -44,6 +43,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class PolarisReactiveLoadBalancerClientFilterTest { + private final MetadataContext testContext = new MetadataContext(); @Mock private LoadBalancerClientFactory clientFactory; @Mock @@ -54,9 +54,7 @@ class PolarisReactiveLoadBalancerClientFilterTest { private ServerWebExchange exchange; @Mock private GatewayFilterChain chain; - private PolarisReactiveLoadBalancerClientFilter polarisFilter; - private final MetadataContext testContext = new MetadataContext(); @BeforeEach void setUp() { @@ -82,11 +80,11 @@ class PolarisReactiveLoadBalancerClientFilterTest { when(originalFilter.filter(exchange, chain)) .thenReturn(Mono.empty()); MetadataContext before = MetadataContextHolder.get(); - Assertions.assertNotEquals(testContext, before); + assertThat(before).isNotEqualTo(testContext); // Act polarisFilter.filter(exchange, chain); MetadataContext after = MetadataContextHolder.get(); - Assertions.assertEquals(testContext, after); + assertThat(after).isEqualTo(testContext); } @Test @@ -102,6 +100,6 @@ class PolarisReactiveLoadBalancerClientFilterTest { MetadataContext after = MetadataContextHolder.get(); // Assert - Assertions.assertEquals(before, after); + assertThat(after).isEqualTo(before); } } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManagerTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManagerTest.java index 5cd7e6b56..be749acb9 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManagerTest.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManagerTest.java @@ -24,7 +24,6 @@ import java.util.Map; import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient; import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -54,7 +53,7 @@ class ContextGatewayPropertiesManagerTest { @Test void shouldHandleEmptyGroupsWhenSettingRouteMap() { // Test empty groups handling - manager.setGroupRouteMap(null); + manager.refreshGroupRoute(null); assertThat(manager.getGroupPathRouteMap()).isEmpty(); assertThat(manager.getGroups()).isNull(); } @@ -81,7 +80,7 @@ class ContextGatewayPropertiesManagerTest { groups.put("group1", group1); // Execute - manager.setGroupRouteMap(groups); + manager.refreshGroupRoute(groups); // Verify classification Map> pathMap = manager.getGroupPathRouteMap(); @@ -123,11 +122,12 @@ class ContextGatewayPropertiesManagerTest { service.setPosition(Position.PATH); predicate.setService(service); group.setPredicate(predicate); - manager.setGroupRouteMap(Collections.singletonMap("testGroup", group)); + manager.refreshGroupRoute(Collections.singletonMap("testGroup", group)); ContextGatewayFilter filter = new ContextGatewayFilter(manager, null); - MockServerHttpRequest request = MockServerHttpRequest.post("http://localhost/context/testNS/testSvc/api/exact").build(); + MockServerHttpRequest request = MockServerHttpRequest.post("http://localhost/context/testNS/testSvc/api/exact") + .build(); String[] apis = filter.rebuildMsApi(request, group, request.getPath().value()); // Test path matching @@ -223,7 +223,6 @@ class ContextGatewayPropertiesManagerTest { "POST|/headerNS/svcFromHeader/api/test", "/api/test"); - } private void testPositionCombination(ApiType apiType, Position namespacePos, Position servicePos, @@ -233,7 +232,7 @@ class ContextGatewayPropertiesManagerTest { group.setRoutes(Collections.singletonList( createContextRoute(expectedMatchPath, "POST", "testNS", "testSvc") )); - manager.setGroupRouteMap(Collections.singletonMap("testGroup", group)); + manager.refreshGroupRoute(Collections.singletonMap("testGroup", group)); // Build test request with appropriate parameters MockServerHttpRequest.BaseBuilder requestBuilder = MockServerHttpRequest.post(inputPath); @@ -270,7 +269,7 @@ class ContextGatewayPropertiesManagerTest { group.setRoutes(Collections.singletonList( createContextRoute("POST|/external/api", "POST", null, null) )); - manager.setGroupRouteMap(Collections.singletonMap("externalGroup", group)); + manager.refreshGroupRoute(Collections.singletonMap("externalGroup", group)); ContextGatewayFilter filter = new ContextGatewayFilter(manager, null); String inputPath = "/context/external/api"; @@ -287,19 +286,19 @@ class ContextGatewayPropertiesManagerTest { void testGroupContext() { GroupContext group1 = new GroupContext(); group1.setComment("testComment"); - Assertions.assertEquals("testComment", group1.getComment()); + assertThat(group1.getComment()).isEqualTo("testComment"); GroupContext.ContextPredicate contextPredicate = new GroupContext.ContextPredicate(); contextPredicate.setContext("testContext"); - Assertions.assertEquals("testContext", contextPredicate.getContext()); + assertThat(contextPredicate.getContext()).isEqualTo("testContext"); GroupContext.ContextRoute contextRoute = new GroupContext.ContextRoute(); contextRoute.setPathMapping("testPathMapping"); - Assertions.assertEquals("testPathMapping", contextRoute.getPathMapping()); + assertThat(contextRoute.getPathMapping()).isEqualTo("testPathMapping"); contextRoute.setHost("testHost"); - Assertions.assertEquals("testHost", contextRoute.getHost()); + assertThat(contextRoute.getHost()).isEqualTo("testHost"); contextRoute.setMetadata(Collections.singletonMap("testKey", "testValue")); - Assertions.assertEquals(1, contextRoute.getMetadata().size()); + assertThat(contextRoute.getMetadata().size()).isEqualTo(1); } } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/context/ContextRoutePredicateFactoryTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/context/ContextRoutePredicateFactoryTest.java index 4c8e6b6ed..0dce2a443 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/context/ContextRoutePredicateFactoryTest.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/context/ContextRoutePredicateFactoryTest.java @@ -17,7 +17,6 @@ package com.tencent.cloud.plugin.gateway.context; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -27,6 +26,7 @@ import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR; /** * Tests for {@link ContextRoutePredicateFactory}. @@ -34,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; class ContextRoutePredicateFactoryTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withBean(ContextGatewayPropertiesManager.class) .withBean(ContextRoutePredicateFactory.class); @Test @@ -46,12 +47,17 @@ class ContextRoutePredicateFactoryTest { // Act ContextRoutePredicateFactory.Config config = factory.newConfig(); config.setGroup("g1"); - Assertions.assertEquals("g1", config.getGroup()); + assertThat(config.getGroup()).isEqualTo("g1"); GatewayPredicate gatewayPredicate = (GatewayPredicate) factory.apply(config); gatewayPredicate.toString(); - Assertions.assertTrue(gatewayPredicate.test(null)); - Assertions.assertEquals(config, gatewayPredicate.getConfig()); + + MockServerWebExchange exchange = MockServerWebExchange.from( + MockServerHttpRequest.get("/test").build()); + exchange.getAttributes().put(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR, "mock"); + + assertThat(gatewayPredicate.test(exchange)).isTrue(); + assertThat(gatewayPredicate.getConfig()).isEqualTo(config); }); } diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-trace-plugin/src/main/java/com/tencent/cloud/plugin/trace/attribute/PolarisSpanAttributesProvider.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-trace-plugin/src/main/java/com/tencent/cloud/plugin/trace/attribute/PolarisSpanAttributesProvider.java index 2ef6bbf75..898bfc02e 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-trace-plugin/src/main/java/com/tencent/cloud/plugin/trace/attribute/PolarisSpanAttributesProvider.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-trace-plugin/src/main/java/com/tencent/cloud/plugin/trace/attribute/PolarisSpanAttributesProvider.java @@ -23,6 +23,7 @@ import java.util.Map; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; import com.tencent.polaris.api.utils.CollectionUtils; import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.metadata.core.MessageMetadataContainer; @@ -84,7 +85,9 @@ public class PolarisSpanAttributesProvider implements SpanAttributesProvider { } attributes.put("http.port", CalleeMetadataContainerGroup.getStaticApplicationMetadataContainer() .getRawMetadataStringValue(MetadataConstants.LOCAL_PORT)); - attributes.put("net.peer.service", context.getTargetServiceInstance().getServiceId()); + EnhancedRequestContext request = context.getRequest(); + String service = request.getServiceUrl() != null ? request.getServiceUrl().getHost() : request.getUrl().getHost(); + attributes.put("net.peer.service", service); String serviceLane = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false) .getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, TRAFFIC_STAIN_LABEL); diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreEnvironmentPostProcessor.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreEnvironmentPostProcessor.java index a55adaba1..e667e8f4f 100644 --- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreEnvironmentPostProcessor.java +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreEnvironmentPostProcessor.java @@ -117,6 +117,17 @@ public final class TsfCoreEnvironmentPostProcessor implements EnvironmentPostPro if (StringUtils.isBlank(tsfNamespaceId)) { LOGGER.error("tsf_namespace_id is empty"); } + else { + String polarisAddress = environment.getProperty("polaris_address"); + if (StringUtils.isBlank(polarisAddress) && StringUtils.isNotBlank(environment.getProperty("spring.cloud.polaris.address"))) { + polarisAddress = environment.getProperty("spring.cloud.polaris.address"); + } + // set tsf_namespace_id as polaris namespace only tsf consul enabled + if (StringUtils.isNotBlank(tsfConsulIp) && StringUtils.isBlank(polarisAddress) && + StringUtils.isBlank(environment.getProperty("spring.cloud.polaris.namespace"))) { + defaultProperties.put("spring.cloud.polaris.namespace", tsfNamespaceId); + } + } // context defaultProperties.put("spring.cloud.polaris.enabled", "true"); diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java index 0d50adcfd..6d632298b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java @@ -18,7 +18,6 @@ package com.tencent.cloud.rpc.enhancement.beanprocessor; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -28,6 +27,7 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; /** @@ -73,7 +73,7 @@ class BlockingLoadBalancerClientBeanPostProcessorTest { Object result = processor.postProcessBeforeInitialization(originalBean, beanName); // Assert - Assertions.assertSame(originalBean, result); + assertThat(result).isSameAs(originalBean); } @Test @@ -82,6 +82,6 @@ class BlockingLoadBalancerClientBeanPostProcessorTest { int order = processor.getOrder(); // Assert - Assertions.assertEquals(BlockingLoadBalancerClientBeanPostProcessor.ORDER, order); + assertThat(order).isEqualTo(BlockingLoadBalancerClientBeanPostProcessor.ORDER); } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilterTest.java index a776b0a4c..dcbb26cbf 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilterTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilterTest.java @@ -42,7 +42,6 @@ import com.tencent.polaris.client.api.SDKContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -177,9 +176,8 @@ public class EnhancedGatewayGlobalFilterTest { EnhancedGatewayGlobalFilter filter = new EnhancedGatewayGlobalFilter(pluginRunner); // Act & Assert - Assertions.assertThrows(CallAbortedException.class, () -> { - filter.filter(exchange, chain); - }); + assertThatThrownBy(() -> filter.filter(exchange, chain)) + .isExactlyInstanceOf(CallAbortedException.class); } @Test diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunctionTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunctionTest.java index 24701dab9..5a29e9161 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunctionTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunctionTest.java @@ -36,7 +36,6 @@ import com.tencent.polaris.api.pojo.CircuitBreakerStatus; import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; import com.tencent.polaris.client.api.SDKContext; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -169,7 +168,7 @@ public class EnhancedWebClientExchangeFilterFunctionTest { // Assert StepVerifier.create(responseMono) .expectNextMatches(response -> { - Assertions.assertEquals(HttpStatus.SERVICE_UNAVAILABLE, response.statusCode()); + assertThat(response.statusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); assertThat(response.headers().asHttpHeaders().containsKey("header-key")).isTrue(); return true; }) diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunnerTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunnerTest.java index 7b5813ee6..85611b22b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunnerTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunnerTest.java @@ -20,13 +20,14 @@ package com.tencent.cloud.rpc.enhancement.plugin; import java.util.ArrayList; import java.util.List; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.cloud.client.serviceregistry.Registration; +import static org.assertj.core.api.Assertions.assertThat; + /** * Tests for {@link DefaultEnhancedPluginRunner}. * @@ -48,7 +49,7 @@ public class DefaultEnhancedPluginRunnerTest { Registration result = DefaultEnhancedPluginRunner.getPolarisRegistration(registrations); // Assert - Assertions.assertSame(normalReg, result); + assertThat(result).isSameAs(normalReg); } // Helper method to create mock Registration objects