diff --git a/CHANGELOG.md b/CHANGELOG.md index da76a8e80b..894650e122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,4 @@ - [feat: implement circuit breaker in enhance plugin, support listen config group, support refresh single config in refresh_context mode.](https://github.com/Tencent/spring-cloud-tencent/pull/1490) - [feat:support polaris event.](https://github.com/Tencent/spring-cloud-tencent/pull/1494) - [feat:support circuit breaker metrics reporting.](https://github.com/Tencent/spring-cloud-tencent/pull/1495) +- [feat: support gateway context, feign eager-load support default value.](https://github.com/Tencent/spring-cloud-tencent/pull/1496) diff --git a/spring-cloud-starter-tencent-all/pom.xml b/spring-cloud-starter-tencent-all/pom.xml index 31c46e67a4..b4c07bf58d 100644 --- a/spring-cloud-starter-tencent-all/pom.xml +++ b/spring-cloud-starter-tencent-all/pom.xml @@ -55,6 +55,11 @@ spring-cloud-starter-tencent-trace-plugin + + com.tencent.cloud + spring-cloud-starter-tencent-gateway-plugin + + com.tencent.cloud spring-cloud-starter-tencent-polaris-auth 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 712a9b9c44..1d3cdf0967 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 @@ -22,6 +22,7 @@ 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.UrlUtils; @@ -95,6 +96,11 @@ public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain web MetadataConstant.HeaderName.METADATA_CONTEXT, MetadataContextHolder.get()); + String targetNamespace = serverWebExchange.getRequest().getHeaders().getFirst(MetadataConstant.HeaderName.NAMESPACE); + if (StringUtils.isNotBlank(targetNamespace)) { + MetadataContextHolder.get().putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE, + MetadataConstant.POLARIS_TARGET_NAMESPACE, targetNamespace); + } TransHeadersTransfer.transfer(serverHttpRequest); return webFilterChain.filter(serverWebExchange) .doFinally((type) -> MetadataContextHolder.remove()); diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reporter/CircuitBreakerPlugin.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reporter/CircuitBreakerPlugin.java index b33295b68b..a7c10c5b9a 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reporter/CircuitBreakerPlugin.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reporter/CircuitBreakerPlugin.java @@ -28,6 +28,7 @@ import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter; +import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; import com.tencent.polaris.metadata.core.MetadataType; import org.slf4j.Logger; @@ -69,10 +70,10 @@ public void run(EnhancedPluginContext context) throws Throwable { EnhancedRequestContext request = context.getRequest(); EnhancedResponseContext response = context.getResponse(); - String governanceNamespace = MetadataContext.LOCAL_NAMESPACE; - String host = request.getServiceUrl() != null ? request.getServiceUrl().getHost() : request.getUrl().getHost(); String path = request.getServiceUrl() != null ? request.getServiceUrl().getPath() : request.getUrl().getPath(); + String governanceNamespace = StringUtils.isNotEmpty(request.getGovernanceNamespace()) ? request.getGovernanceNamespace() : MetadataContext.LOCAL_NAMESPACE; + String httpMethod = request.getHttpMethod().name(); CircuitBreaker circuitBreaker = circuitBreakerFactory.create(governanceNamespace + "#" + host + "#" + path + "#http#" + httpMethod); diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java index a903d1f999..3c8b1bf4f5 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java @@ -28,6 +28,7 @@ import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.enums.ConfigFileFormat; import com.tencent.cloud.polaris.context.config.PolarisContextProperties; +import com.tencent.polaris.api.utils.ClassUtils; import com.tencent.polaris.configuration.api.core.ConfigFileMetadata; import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.api.core.ConfigKVFile; @@ -213,13 +214,17 @@ void initTsfConfigGroups(CompositePropertySource compositePropertySource) { String tsfNamespaceName = environment.getProperty("tsf_namespace_name"); String tsfGroupName = environment.getProperty("tsf_group_name"); - if (StringUtils.isEmpty(tsfId) || StringUtils.isEmpty(tsfNamespaceName) || StringUtils.isEmpty(tsfGroupName)) { + if (StringUtils.isEmpty(tsfNamespaceName) || StringUtils.isEmpty(tsfGroupName)) { return; } String namespace = polarisContextProperties.getNamespace(); - List tsfConfigGroups = Arrays.asList( - tsfId + "." + tsfGroupName + ".application_config_group", - tsfId + "." + tsfNamespaceName + ".global_config_group"); + List tsfConfigGroups = new ArrayList<>(); + tsfConfigGroups.add((StringUtils.hasText(tsfId) ? tsfId + "." : "") + tsfGroupName + ".application_config_group"); + tsfConfigGroups.add((StringUtils.hasText(tsfId) ? tsfId + "." : "") + tsfNamespaceName + ".global_config_group"); + + if (ClassUtils.isClassPresent("org.springframework.cloud.gateway.filter.GlobalFilter")) { + tsfConfigGroups.add((StringUtils.hasText(tsfId) ? tsfId + "." : "") + tsfGroupName + ".gateway_config_group"); + } for (String tsfConfigGroup : tsfConfigGroups) { PolarisPropertySource polarisPropertySource = loadGroupPolarisPropertySource(configFileService, namespace, tsfConfigGroup); if (polarisPropertySource == null) { diff --git a/spring-cloud-starter-tencent-polaris-discovery/pom.xml b/spring-cloud-starter-tencent-polaris-discovery/pom.xml index 35c87ef0de..0086854c9a 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/pom.xml +++ b/spring-cloud-starter-tencent-polaris-discovery/pom.xml @@ -58,6 +58,18 @@ + + org.springframework.cloud + spring-cloud-openfeign-core + true + + + + io.github.openfeign + feign-core + true + + org.springframework.boot spring-boot-starter-web diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryHandler.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryHandler.java index ee023a25c4..e1c5ac361b 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryHandler.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryHandler.java @@ -17,6 +17,9 @@ package com.tencent.cloud.polaris.discovery; +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.polaris.PolarisDiscoveryProperties; import com.tencent.cloud.polaris.context.PolarisSDKContextManager; import com.tencent.polaris.api.core.ConsumerAPI; @@ -49,7 +52,9 @@ public PolarisDiscoveryHandler(PolarisDiscoveryProperties polarisDiscoveryProper * @return list of healthy instances */ public InstancesResponse getHealthyInstances(String service) { - String namespace = polarisDiscoveryProperties.getNamespace(); + String namespace = MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_APPLICATION_NONE, + MetadataConstant.POLARIS_TARGET_NAMESPACE, polarisDiscoveryProperties.getNamespace()); + GetHealthyInstancesRequest getHealthyInstancesRequest = new GetHealthyInstancesRequest(); getHealthyInstancesRequest.setNamespace(namespace); getHealthyInstancesRequest.setService(service); diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/config/PolarisEagerLoadAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/config/PolarisEagerLoadAutoConfiguration.java index 179833b675..7edf653e79 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/config/PolarisEagerLoadAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/config/PolarisEagerLoadAutoConfiguration.java @@ -29,7 +29,7 @@ import org.springframework.context.annotation.Configuration; @Configuration -@ConditionalOnProperty(name = "spring.cloud.polaris.discovery.eager-load.enabled", havingValue = "true") +@ConditionalOnProperty(name = "spring.cloud.polaris.discovery.eager-load.enabled", havingValue = "true", matchIfMissing = true) public class PolarisEagerLoadAutoConfiguration { @Bean diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java index ca4e0e0a9e..4ede8529cf 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java @@ -17,13 +17,17 @@ package com.tencent.cloud.polaris.eager.instrument.feign; -import com.tencent.cloud.common.util.FeignUtil; +import java.lang.reflect.Field; +import java.lang.reflect.Proxy; + import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient; import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient; import com.tencent.polaris.api.utils.StringUtils; +import feign.Target; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.ApplicationContext; import org.springframework.context.SmartLifecycle; @@ -47,36 +51,59 @@ public FeignEagerLoadSmartLifecycle(ApplicationContext applicationContext, Polar @Override public void start() { LOG.info("feign eager-load start"); - for (String name : applicationContext.getBeanDefinitionNames()) { + for (Object bean : applicationContext.getBeansWithAnnotation(FeignClient.class).values()) { try { - if (name.contains(FeignUtil.FEIGN_CLIENT_SPECIF) && !name.startsWith(FeignUtil.FEIGN_CLIENT_DEFAULT)) { - String feignName = FeignUtil.analysisFeignName(name, applicationContext); - if (StringUtils.isNotBlank(feignName)) { - LOG.info("[{}] eager-load start", feignName); - if (polarisDiscoveryClient != null) { - polarisDiscoveryClient.getInstances(feignName); - } - else if (polarisReactiveDiscoveryClient != null) { - polarisReactiveDiscoveryClient.getInstances(feignName).subscribe(); - } - else { - LOG.warn("[{}] no discovery client found.", feignName); + if (Proxy.isProxyClass(bean.getClass())) { + Target.HardCodedTarget hardCodedTarget = getHardCodedTarget(bean); + if (hardCodedTarget != null) { + FeignClient feignClient = hardCodedTarget.type().getAnnotation(FeignClient.class); + // if feignClient contains url, it doesn't need to eager load. + if (StringUtils.isEmpty(feignClient.url())) { + // support variables and default values. + String feignName = hardCodedTarget.name(); + LOG.info("[{}] eager-load start", feignName); + if (polarisDiscoveryClient != null) { + polarisDiscoveryClient.getInstances(feignName); + } + else if (polarisReactiveDiscoveryClient != null) { + polarisReactiveDiscoveryClient.getInstances(feignName).subscribe(); + } + else { + LOG.warn("[{}] no discovery client found.", feignName); + } + LOG.info("[{}] eager-load end", feignName); } - LOG.info("[{}] eager-load end", feignName); - } - else { - LOG.warn("feign name is blank."); } } } catch (Exception e) { - LOG.error("[{}] eager-load failed.", name, e); + LOG.debug("[{}] eager-load failed.", bean, e); } } LOG.info("feign eager-load end"); } + public static Target.HardCodedTarget getHardCodedTarget(Object proxy) { + try { + Object invocationHandler = Proxy.getInvocationHandler(proxy); + + for (Field field : invocationHandler.getClass().getDeclaredFields()) { + field.setAccessible(true); + Object fieldValue = field.get(invocationHandler); + if (fieldValue instanceof Target.HardCodedTarget) { + return (Target.HardCodedTarget) fieldValue; + } + } + } + catch (Exception e) { + if (LOG.isDebugEnabled()) { + LOG.debug("proxy:{}, getTarget failed.", proxy, e); + } + } + return null; + } + @Override public void stop() { diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfServletRegistrationCustomizer.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfServletRegistrationCustomizer.java index 08ca276ee5..7296e52c50 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfServletRegistrationCustomizer.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfServletRegistrationCustomizer.java @@ -29,7 +29,6 @@ import com.tencent.cloud.polaris.registry.PolarisRegistrationCustomizer; import com.tencent.polaris.plugins.connector.common.constant.ConsulConstant; - import org.springframework.beans.factory.ObjectProvider; import org.springframework.util.StringUtils; diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterUtils.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterUtils.java index cb295da8c5..0e5f2b5b04 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterUtils.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterUtils.java @@ -25,7 +25,9 @@ import java.util.function.Function; import java.util.stream.Collectors; +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.rpc.enhancement.transformer.InstanceTransformer; import com.tencent.polaris.api.pojo.DefaultServiceInstances; import com.tencent.polaris.api.pojo.Instance; @@ -72,7 +74,10 @@ public static ServiceInstances transferServersToServiceInstances(Flux getMetadataAsMap(MetadataType metadataType, TransitiveType transitiveType, boolean caller) { MetadataContainer metadataContainer = getMetadataContainer(metadataType, caller); Map values = new HashMap<>(); @@ -266,6 +274,8 @@ public Map getFragmentContext(String fragment) { return getMetadataAsMap(MetadataType.CUSTOM, TransitiveType.DISPOSABLE, true); case FRAGMENT_APPLICATION: return getMetadataAsMap(MetadataType.APPLICATION, TransitiveType.DISPOSABLE, false); + case FRAGMENT_APPLICATION_NONE: + return getMetadataAsMap(MetadataType.APPLICATION, TransitiveType.NONE, false); case FRAGMENT_UPSTREAM_APPLICATION: return getMetadataAsMap(MetadataType.APPLICATION, TransitiveType.DISPOSABLE, true); case FRAGMENT_RAW_TRANSHEADERS: @@ -277,6 +287,10 @@ public Map getFragmentContext(String fragment) { } } + public String getContext(String fragment, String key, String defaultValue) { + return getFragmentContext(fragment).getOrDefault(key, defaultValue); + } + public String getContext(String fragment, String key) { Map fragmentContext = getFragmentContext(fragment); if (fragmentContext == null) { @@ -305,6 +319,9 @@ public void putFragmentContext(String fragment, Map context) { case FRAGMENT_APPLICATION: putMetadataAsMap(MetadataType.APPLICATION, TransitiveType.DISPOSABLE, false, context); break; + case FRAGMENT_APPLICATION_NONE: + putMetadataAsMap(MetadataType.APPLICATION, TransitiveType.NONE, false, context); + break; case FRAGMENT_UPSTREAM_APPLICATION: putMetadataAsMap(MetadataType.APPLICATION, TransitiveType.DISPOSABLE, true, context); break; @@ -320,7 +337,9 @@ public void putFragmentContext(String fragment, Map context) { } } - public static void setLocalService(String service) { - LOCAL_SERVICE = service; + public void putFragmentContext(String fragment, String key, String value) { + Map context = new HashMap<>(1); + context.put(key, value); + putFragmentContext(fragment, context); } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/FeignUtil.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/FeignUtil.java deleted file mode 100644 index 4306e087ef..0000000000 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/FeignUtil.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.common.util; - -import org.springframework.context.ApplicationContext; -import org.springframework.util.StringUtils; - -/** - * @author heihuliliu - */ -public final class FeignUtil { - - /** - * Feign client spec. - */ - public static final String FEIGN_CLIENT_SPECIF = ".FeignClientSpecification"; - - /** - * Default Feign client spec. - */ - public static final String FEIGN_CLIENT_DEFAULT = "default."; - - /** - * regular expression that parses ${xxx} . - */ - public static final String REGEX = "^[$][{](.*)[}]$"; - - /** - * replacement of ${xxx}. - */ - public static final String REPLACEMENT = "$1"; - - private FeignUtil() { - - } - - /** - * TODO If @FeignClient specifies contextId, the service name will not be obtained correctly, but the contextId will be obtained. - * - * @param name feign name. - * @param context application context. - * @return service name. - */ - public static String analysisFeignName(String name, ApplicationContext context) { - String feignName = ""; - String feignPath = name.substring(0, name.indexOf(FEIGN_CLIENT_SPECIF)); - // Handle the case where the service name is a variable - if (feignPath.matches(REGEX)) { - feignPath = context.getEnvironment().getProperty(feignPath.replaceAll(REGEX, REPLACEMENT)); - } - if (StringUtils.hasText(feignPath)) { - // The case of multi-level paths - String[] feignNames = feignPath.split("/"); - if (feignNames.length > 1) { - for (int i = 0; i < feignNames.length; i++) { - if (StringUtils.hasText(feignNames[i])) { - feignName = feignNames[i]; - break; - } - } - } - else { - feignName = feignNames[0]; - } - } - return feignName; - } -} diff --git a/spring-cloud-tencent-coverage/pom.xml b/spring-cloud-tencent-coverage/pom.xml index f3fa076f1f..7ec3c91a40 100644 --- a/spring-cloud-tencent-coverage/pom.xml +++ b/spring-cloud-tencent-coverage/pom.xml @@ -86,7 +86,7 @@ com.tencent.cloud - spring-cloud-tencent-gateway-plugin + spring-cloud-starter-tencent-gateway-plugin diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index 15da36e3cb..62284f878a 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -194,7 +194,7 @@ com.tencent.cloud - spring-cloud-tencent-gateway-plugin + spring-cloud-starter-tencent-gateway-plugin ${revision} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/pom.xml index d8752b721d..72578faf82 100644 --- a/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/pom.xml +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/pom.xml @@ -20,7 +20,7 @@ com.tencent.cloud - spring-cloud-tencent-gateway-plugin + spring-cloud-starter-tencent-gateway-plugin com.tencent.cloud diff --git a/spring-cloud-tencent-examples/quickstart-example/quickstart-gateway-service/pom.xml b/spring-cloud-tencent-examples/quickstart-example/quickstart-gateway-service/pom.xml index 9bfb76041a..4428cc570d 100644 --- a/spring-cloud-tencent-examples/quickstart-example/quickstart-gateway-service/pom.xml +++ b/spring-cloud-tencent-examples/quickstart-example/quickstart-gateway-service/pom.xml @@ -21,7 +21,7 @@ com.tencent.cloud - spring-cloud-tencent-gateway-plugin + spring-cloud-starter-tencent-gateway-plugin diff --git a/spring-cloud-tencent-examples/quickstart-example/quickstart-gateway-service/src/main/resources/application.yml b/spring-cloud-tencent-examples/quickstart-example/quickstart-gateway-service/src/main/resources/application.yml index d40fd6bf2c..d45c52e7bd 100644 --- a/spring-cloud-tencent-examples/quickstart-example/quickstart-gateway-service/src/main/resources/application.yml +++ b/spring-cloud-tencent-examples/quickstart-example/quickstart-gateway-service/src/main/resources/application.yml @@ -43,13 +43,6 @@ spring: args: regexp: '''/'' + serviceId + ''/(?.*)''' replacement: '''/$\{remaining}''' - 'filters[1]': - name: CircuitBreaker - args: - # statusCodes 缺省时会自动识别 "5**" 为错误 - # statusCodes: '''404,5**''' - # fallbackUri 缺省时会在熔断触发后拉取 plaris server 配置的降级作为 response - fallbackUri: '''forward:/polaris-fallback''' routes: - id: QuickstartCallerService uri: lb://QuickstartCallerService diff --git a/spring-cloud-tencent-plugin-starters/pom.xml b/spring-cloud-tencent-plugin-starters/pom.xml index 4159c7843e..67ad734d84 100644 --- a/spring-cloud-tencent-plugin-starters/pom.xml +++ b/spring-cloud-tencent-plugin-starters/pom.xml @@ -16,7 +16,7 @@ spring-cloud-tencent-featureenv-plugin - spring-cloud-tencent-gateway-plugin + spring-cloud-starter-tencent-gateway-plugin spring-cloud-starter-tencent-discovery-adapter-plugin spring-cloud-tencent-lossless-plugin spring-cloud-starter-tencent-threadlocal-plugin diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml similarity index 80% rename from spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml rename to spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml index 264dc78b55..49536e26c0 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml @@ -10,8 +10,8 @@ 4.0.0 - spring-cloud-tencent-gateway-plugin - Spring Cloud Tencent Gateway Plugin + spring-cloud-starter-tencent-gateway-plugin + Spring Cloud Starter Tencent Gateway Plugin @@ -19,6 +19,12 @@ spring-cloud-starter-tencent-polaris-config + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-discovery + true + + org.springframework.cloud spring-cloud-gateway-server 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 new file mode 100644 index 0000000000..98a7a3636b --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/GatewayPluginAutoConfiguration.java @@ -0,0 +1,109 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway; + +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.polaris.config.ConditionalOnPolarisConfigEnabled; +import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient; +import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; + +/** + * Auto configuration for spring cloud gateway plugins. + * @author lepdou 2022-07-06 + */ +@Configuration +@ConditionalOnPolarisEnabled +@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.enabled", matchIfMissing = true) +public class GatewayPluginAutoConfiguration { + + @Configuration + @ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.context.enabled", matchIfMissing = true) + @ConditionalOnPolarisConfigEnabled + @ConditionalOnClass(GlobalFilter.class) + @Import(ContextGatewayProperties.class) + public static class ContextPluginConfiguration { + + @Value("${spring.cloud.polaris.discovery.eager-load.enabled:#{'true'}}") + private boolean commonEagerLoadEnabled; + + @Value("${spring.cloud.polaris.discovery.eager-load.gateway.enabled:#{'true'}}") + private boolean gatewayEagerLoadEnabled; + + @Bean + public ContextGatewayFilterFactory contextGatewayFilterFactory(ContextGatewayPropertiesManager contextGatewayPropertiesManager) { + return new ContextGatewayFilterFactory(contextGatewayPropertiesManager); + } + + @Bean + public ContextPropertiesRouteDefinitionLocator contextPropertiesRouteDefinitionLocator(ContextGatewayProperties properties) { + return new ContextPropertiesRouteDefinitionLocator(properties); + } + + @Bean + public ContextRoutePredicateFactory contextServiceRoutePredicateFactory() { + return new ContextRoutePredicateFactory(); + } + + @Bean + public ContextGatewayPropertiesManager contextGatewayPropertiesManager(ContextGatewayProperties properties, + @Autowired(required = false) PolarisDiscoveryClient polarisDiscoveryClient, + @Autowired(required = false) PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) { + ContextGatewayPropertiesManager contextGatewayPropertiesManager = new ContextGatewayPropertiesManager(); + contextGatewayPropertiesManager.setGroupRouteMap(properties.getGroups()); + if (commonEagerLoadEnabled && gatewayEagerLoadEnabled) { + contextGatewayPropertiesManager.eagerLoad(polarisDiscoveryClient, polarisReactiveDiscoveryClient); + } + return contextGatewayPropertiesManager; + } + + @Bean + public GatewayRegistrationCustomizer gatewayRegistrationCustomizer() { + return new GatewayRegistrationCustomizer(); + } + + @Bean + public GatewayConfigChangeListener gatewayConfigChangeListener(ContextGatewayPropertiesManager manager, + ApplicationEventPublisher publisher, Environment environment) { + return new GatewayConfigChangeListener(manager, publisher, environment); + } + + @Bean + public PolarisReactiveLoadBalancerClientFilterBeanPostProcessor polarisReactiveLoadBalancerClientFilterBeanPostProcessor( + ApplicationContext applicationContext) { + return new PolarisReactiveLoadBalancerClientFilterBeanPostProcessor(applicationContext); + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/GatewayRegistrationCustomizer.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/GatewayRegistrationCustomizer.java new file mode 100644 index 0000000000..9888991272 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/GatewayRegistrationCustomizer.java @@ -0,0 +1,28 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway; + +import com.tencent.cloud.polaris.registry.PolarisRegistration; +import com.tencent.cloud.polaris.registry.PolarisRegistrationCustomizer; + +public class GatewayRegistrationCustomizer implements PolarisRegistrationCustomizer { + @Override + public void customize(PolarisRegistration registration) { + registration.getMetadata().put("internal-service-type", "spring-cloud-gateway"); + } +} 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 new file mode 100644 index 0000000000..538f0e4594 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilter.java @@ -0,0 +1,56 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway; + +import com.tencent.cloud.common.constant.MetadataConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.web.server.ServerWebExchange; + +public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter { + + private final ReactiveLoadBalancerClientFilter clientFilter; + + public PolarisReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, + GatewayLoadBalancerProperties properties, ReactiveLoadBalancerClientFilter clientFilter) { + super(clientFactory, properties); + this.clientFilter = clientFilter; + } + + @Override + public int getOrder() { + return clientFilter.getOrder(); + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + // restore context from exchange + MetadataContext metadataContext = (MetadataContext) exchange.getAttributes().get( + MetadataConstant.HeaderName.METADATA_CONTEXT); + if (metadataContext != null) { + MetadataContextHolder.set(metadataContext); + } + return clientFilter.filter(exchange, chain); + } +} 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 new file mode 100644 index 0000000000..3055984f2a --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.java @@ -0,0 +1,51 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; +import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.core.Ordered; + +public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessor implements BeanPostProcessor, Ordered { + + private ApplicationContext applicationContext; + + public PolarisReactiveLoadBalancerClientFilterBeanPostProcessor(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof ReactiveLoadBalancerClientFilter && !(bean instanceof PolarisReactiveLoadBalancerClientFilter)) { + LoadBalancerClientFactory clientFactory = applicationContext.getBean(LoadBalancerClientFactory.class); + GatewayLoadBalancerProperties properties = applicationContext.getBean(GatewayLoadBalancerProperties.class); + + return new PolarisReactiveLoadBalancerClientFilter(clientFactory, properties, (ReactiveLoadBalancerClientFilter) bean); + } + return bean; + } + + @Override + public int getOrder() { + return 0; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ApiType.java similarity index 58% rename from spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java rename to spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ApiType.java index d927f362c6..4679e804c7 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ApiType.java @@ -15,23 +15,15 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.plugin.gateway.staining; - -import java.util.Map; - -import org.springframework.core.Ordered; -import org.springframework.web.server.ServerWebExchange; - -/** - * Staining according to request parameters. for example, when the request parameter uid=0, staining env=blue. - * @author lepdou 2022-07-06 - */ -public interface TrafficStainer extends Ordered { +package com.tencent.cloud.plugin.gateway.context; +public enum ApiType { + /** + * Ms api type. + */ + MS, /** - * get stained labels from request. - * @param exchange the request. - * @return stained labels. + * External api type. */ - Map apply(ServerWebExchange exchange); + EXTERNAL, } 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 new file mode 100644 index 0000000000..261e45c049 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilter.java @@ -0,0 +1,199 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import java.lang.reflect.Constructor; +import java.net.URI; +import java.util.HashMap; + +import com.tencent.cloud.common.constant.MetadataConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.polaris.api.utils.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; + +public class ContextGatewayFilter implements GatewayFilter, Ordered { + + private static final Logger logger = LoggerFactory.getLogger(ContextGatewayFilter.class); + + private ContextGatewayPropertiesManager manager; + + private ContextGatewayFilterFactory.Config config; + + public ContextGatewayFilter(ContextGatewayPropertiesManager manager, ContextGatewayFilterFactory.Config config) { + this.manager = manager; + this.config = config; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + GroupContext groupContext = manager.getGroups().get(config.getGroup()); + + if (ApiType.MS.equals(groupContext.getPredicate().getApiType())) { + return msFilter(exchange, chain, groupContext); + } + else { + return externalFilter(exchange, chain, groupContext); + } + } + + private Mono externalFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext) { + ServerHttpRequest request = exchange.getRequest(); + String[] apis = rebuildExternalApi(request, request.getPath().value()); + GroupContext.ContextRoute contextRoute = manager.getGroupPathRoute(config.getGroup(), apis[0]); + if (contextRoute == null) { + throw new RuntimeException(String.format("Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath())); + } + updateRouteMetadata(exchange, contextRoute); + + URI requestUri = URI.create(contextRoute.getHost() + apis[1]); + exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri); + // 调整为正确路径 + ServerHttpRequest newRequest = request.mutate().path(apis[1]).build(); + return chain.filter(exchange.mutate().request(newRequest).build()); + } + + private Mono msFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext) { + ServerHttpRequest request = exchange.getRequest(); + String[] apis = rebuildMsApi(request, groupContext, request.getPath().value()); + // 判断 api 是否匹配 + GroupContext.ContextRoute contextRoute = manager.getGroupPathRoute(config.getGroup(), apis[0]); + if (contextRoute == null) { + throw new RuntimeException(String.format("Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath())); + } + updateRouteMetadata(exchange, contextRoute); + + MetadataContext metadataContext = (MetadataContext) exchange.getAttributes().get( + MetadataConstant.HeaderName.METADATA_CONTEXT); + + metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE, + MetadataConstant.POLARIS_TARGET_NAMESPACE, contextRoute.getNamespace()); + + URI requestUri = URI.create("lb://" + contextRoute.getService() + apis[1]); + exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri); + // 调整为正确路径 + ServerHttpRequest newRequest = request.mutate().path(apis[1]).build(); + return chain.filter(exchange.mutate().request(newRequest).build()); + } + + /** + * e.g. "/context/api/test" → [ "GET|/api/test", "/api/test"] + */ + private String[] rebuildExternalApi(ServerHttpRequest request, String path) { + String[] pathSegments = path.split("/"); + StringBuilder matchPath = new StringBuilder(); + StringBuilder realPath = new StringBuilder(); + int index = 2; + matchPath.append(request.getMethodValue()).append("|"); + for (int i = index; i < pathSegments.length; i++) { + matchPath.append("/").append(pathSegments[i]); + realPath.append("/").append(pathSegments[i]); + } + if (path.endsWith("/")) { + matchPath.append("/"); + realPath.append("/"); + } + return new String[] {matchPath.toString(), realPath.toString()}; + } + + /** + * returns an array of two strings, the first is the match path, the second is the real path. + * e.g. "/context/namespace/svc/api/test" → [ "GET|/namespace/svc/api/test", "/api/test"] + */ + private String[] rebuildMsApi(ServerHttpRequest request, GroupContext groupContext, String path) { + String[] pathSegments = path.split("/"); + StringBuilder matchPath = new StringBuilder(); + int index = 2; + matchPath.append(request.getMethodValue()).append("|"); + + 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: + matchPath.append("/").append(pathSegments[index++]); + 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: + matchPath.append("/").append(pathSegments[index++]); + } + StringBuilder realPath = new StringBuilder(); + for (int i = index; i < pathSegments.length; i++) { + matchPath.append("/").append(pathSegments[i]); + realPath.append("/").append(pathSegments[i]); + } + if (path.endsWith("/")) { + matchPath.append("/"); + realPath.append("/"); + } + + return new String[] {matchPath.toString(), realPath.toString()}; + } + + private void updateRouteMetadata(ServerWebExchange exchange, GroupContext.ContextRoute contextRoute) { + if (CollectionUtils.isEmpty(contextRoute.getMetadata())) { + return; + } + + Route route = (Route) exchange.getAttributes().get(GATEWAY_ROUTE_ATTR); + Constructor constructor = Route.class.getDeclaredConstructors()[1]; + constructor.setAccessible(true); + try { + HashMap metadata = new HashMap<>(route.getMetadata()); + metadata.putAll(contextRoute.getMetadata()); + Route newRoute = (Route) constructor.newInstance(route.getId(), route.getUri(), + route.getOrder(), route.getPredicate(), route.getFilters(), metadata); + exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, newRoute); + } + catch (Exception e) { + logger.debug("[updateRouteMetadata] update route metadata failed", e); + } + } + + @Override + public int getOrder() { + // after RouteToRequestUrlFilter, DecodeTransferMetadataReactiveFilter + return RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 12; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilterFactory.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilterFactory.java new file mode 100644 index 0000000000..2293bee6fc --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilterFactory.java @@ -0,0 +1,58 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; + +public class ContextGatewayFilterFactory extends AbstractGatewayFilterFactory { + + private ContextGatewayPropertiesManager manager; + + public ContextGatewayFilterFactory(ContextGatewayPropertiesManager manager) { + super(Config.class); + this.manager = manager; + } + + @Override + public List shortcutFieldOrder() { + return Arrays.asList("group"); + } + + @Override + public GatewayFilter apply(Config config) { + return new ContextGatewayFilter(manager, config); + } + + public static class Config { + private String group; + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + } + + +} 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 new file mode 100644 index 0000000000..f4d07541a5 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayProperties.java @@ -0,0 +1,70 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.gateway.route.RouteDefinition; +import org.springframework.core.style.ToStringCreator; + +@ConfigurationProperties(ContextGatewayProperties.PREFIX) +public class ContextGatewayProperties { + + /** + * Properties prefix. + */ + public static final String PREFIX = "spring.cloud.tencent.gateway"; + + private final Log logger = LogFactory.getLog(getClass()); + + private Map routes = new HashMap<>(); + + private Map groups = new HashMap<>(); + + public Map getRoutes() { + return routes; + } + + public void setRoutes(Map routes) { + this.routes = routes; + if (routes != null && routes.size() > 0 && logger.isDebugEnabled()) { + logger.debug("Routes supplied from Gateway Properties: " + routes); + } + } + + public Map getGroups() { + return groups; + } + + public void setGroups(Map groups) { + this.groups = groups; + } + + @Override + public String toString() { + return new ToStringCreator(this).append("routes", routes) + .append("groups", groups).toString(); + + } + +} 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 new file mode 100644 index 0000000000..ae491587e3 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java @@ -0,0 +1,150 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +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.polaris.discovery.PolarisDiscoveryClient; +import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient; +import com.tencent.polaris.api.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.util.AntPathMatcher; + +public class ContextGatewayPropertiesManager { + + private static final Logger logger = LoggerFactory.getLogger(ContextGatewayPropertiesManager.class); + /** + * context -> {path key -> route}. + */ + private volatile ConcurrentHashMap> groupPathRouteMap = new ConcurrentHashMap<>(); + /** + * context -> {wildcard path key -> route}. + */ + private volatile ConcurrentHashMap> groupWildcardPathRouteMap = new ConcurrentHashMap<>(); + + private Map groups = new HashMap<>(); + + private AntPathMatcher antPathMatcher = new AntPathMatcher(); + + public Map> getGroupPathRouteMap() { + return groupPathRouteMap; + } + + public void setGroupRouteMap(Map groups) { + + ConcurrentHashMap> newGroupPathRouteMap = new ConcurrentHashMap<>(); + ConcurrentHashMap> newGroupWildcardPathRouteMap = new ConcurrentHashMap<>(); + if (groups != null) { + for (Map.Entry entry : groups.entrySet()) { + Map newGroupPathRoute = new HashMap<>(); + Map newGroupWildcardPathRoute = new HashMap<>(); + for (GroupContext.ContextRoute route : entry.getValue().getRoutes()) { + String path = route.getPath(); + // convert path parameter to group wildcard path + if (path.contains("{") && path.contains("}") || path.contains("*")) { + newGroupWildcardPathRoute.put(buildPathKey(entry.getValue(), route), route); + } + else { + newGroupPathRoute.put(buildPathKey(entry.getValue(), route), route); + } + } + newGroupWildcardPathRouteMap.put(entry.getKey(), newGroupWildcardPathRoute); + newGroupPathRouteMap.put(entry.getKey(), newGroupPathRoute); + } + } + this.groupPathRouteMap = newGroupPathRouteMap; + this.groupWildcardPathRouteMap = newGroupWildcardPathRouteMap; + this.groups = groups; + } + + public Map getGroups() { + return groups; + } + + public GroupContext.ContextRoute getGroupPathRoute(String group, String path) { + Map groupPathRouteMap = this.groupPathRouteMap.get(group); + if (groupPathRouteMap != null && groupPathRouteMap.containsKey(path)) { + return groupPathRouteMap.get(path); + } + + Map groupWildcardPathRouteMap = this.groupWildcardPathRouteMap.get(group); + if (groupWildcardPathRouteMap != null) { + for (Map.Entry entry : groupWildcardPathRouteMap.entrySet()) { + boolean matched = antPathMatcher.match(entry.getKey(), path); + if (matched) { + return entry.getValue(); + } + } + } + return null; + } + + public void eagerLoad(PolarisDiscoveryClient polarisDiscoveryClient, + PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) { + for (Map contextRouteMap : groupPathRouteMap.values()) { + for (GroupContext.ContextRoute contextRoute : contextRouteMap.values()) { + eagerLoadFromRoute(contextRoute, polarisDiscoveryClient, polarisReactiveDiscoveryClient); + } + } + for (Map contextRouteMap : groupWildcardPathRouteMap.values()) { + for (GroupContext.ContextRoute contextRoute : contextRouteMap.values()) { + eagerLoadFromRoute(contextRoute, polarisDiscoveryClient, polarisReactiveDiscoveryClient); + } + } + } + + private void eagerLoadFromRoute(GroupContext.ContextRoute contextRoute, PolarisDiscoveryClient polarisDiscoveryClient, + PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) { + String namespace = contextRoute.getNamespace(); + String service = contextRoute.getService(); + if (StringUtils.isNotEmpty(namespace) && StringUtils.isNotEmpty(service)) { + logger.info("[{},{}] eager-load start", namespace, service); + MetadataContextHolder.get().putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE, + MetadataConstant.POLARIS_TARGET_NAMESPACE, namespace); + + if (polarisDiscoveryClient != null) { + polarisDiscoveryClient.getInstances(service); + } + else if (polarisReactiveDiscoveryClient != null) { + polarisReactiveDiscoveryClient.getInstances(service).subscribe(); + } + else { + logger.warn("[{}] no discovery client found.", service); + } + logger.info("[{},{}] eager-load end", namespace, service); + } + } + + 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()); + } + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextPropertiesRouteDefinitionLocator.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextPropertiesRouteDefinitionLocator.java new file mode 100644 index 0000000000..366b48b3af --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextPropertiesRouteDefinitionLocator.java @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import reactor.core.publisher.Flux; + +import org.springframework.cloud.gateway.route.RouteDefinition; +import org.springframework.cloud.gateway.route.RouteDefinitionLocator; + +public class ContextPropertiesRouteDefinitionLocator implements RouteDefinitionLocator { + + private final ContextGatewayProperties properties; + + public ContextPropertiesRouteDefinitionLocator(ContextGatewayProperties properties) { + this.properties = properties; + } + + @Override + public Flux getRouteDefinitions() { + return Flux.fromIterable(this.properties.getRoutes().values()); + } +} 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 new file mode 100644 index 0000000000..cce94d665c --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextRoutePredicateFactory.java @@ -0,0 +1,78 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; +import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate; +import org.springframework.web.server.ServerWebExchange; + +public class ContextRoutePredicateFactory extends AbstractRoutePredicateFactory { + + public ContextRoutePredicateFactory() { + super(Config.class); + } + + @Override + public Predicate apply(Config config) { + return new GatewayPredicate() { + @Override + public boolean test(ServerWebExchange exchange) { + // TODO: do path-rewriting , put to GATEWAY_PREDICATE_PATH_CONTAINER_ATTR + return true; + } + + @Override + public Object getConfig() { + return config; + } + + @Override + public String toString() { + return String.format("Config: %s", config); + } + }; + } + + @Override + public List shortcutFieldOrder() { + return Arrays.asList("group"); + } + + public static class Config { + private String group; + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + @Override + public String toString() { + return "Config{" + + "group='" + group + '\'' + + '}'; + } + } +} 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 new file mode 100644 index 0000000000..50b299856c --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GatewayConfigChangeListener.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import com.tencent.cloud.polaris.config.annotation.PolarisConfigKVFileChangeListener; +import com.tencent.cloud.polaris.config.listener.ConfigChangeEvent; + +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.cloud.gateway.config.GatewayProperties; +import org.springframework.cloud.gateway.event.RefreshRoutesEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.env.Environment; + +public class GatewayConfigChangeListener { + + private ApplicationEventPublisher publisher; + + private ContextGatewayPropertiesManager manager; + + private Environment environment; + + public GatewayConfigChangeListener(ContextGatewayPropertiesManager manager, + ApplicationEventPublisher publisher, Environment environment) { + this.manager = manager; + this.publisher = publisher; + this.environment = environment; + } + + @PolarisConfigKVFileChangeListener(interestedKeyPrefixes = ContextGatewayProperties.PREFIX) + public void onChangeTencentGatewayProperties(ConfigChangeEvent event) { + Binder binder = Binder.get(environment); + BindResult result = binder.bind(ContextGatewayProperties.PREFIX, ContextGatewayProperties.class); + manager.setGroupRouteMap(result.get().getGroups()); + this.publisher.publishEvent(new RefreshRoutesEvent(event)); + } + + @PolarisConfigKVFileChangeListener(interestedKeyPrefixes = GatewayProperties.PREFIX) + public void onChangeGatewayConfigChangeListener(ConfigChangeEvent event) { + 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/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 new file mode 100644 index 0000000000..6394cffc49 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/GroupContext.java @@ -0,0 +1,223 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +import java.util.List; +import java.util.Map; + +public class GroupContext { + + private String comment; + + private ApiType apiType; + + private ContextPredicate predicate; + + private List routes; + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public ApiType getApiType() { + return apiType; + } + + public void setApiType(ApiType apiType) { + this.apiType = apiType; + } + + public ContextPredicate getPredicate() { + return predicate; + } + + public void setPredicate(ContextPredicate predicate) { + this.predicate = predicate; + } + + public List getRoutes() { + return routes; + } + + public void setRoutes(List routes) { + this.routes = routes; + } + + public static class ContextPredicate { + private ApiType apiType; + + private String context; + + private ContextNamespace namespace; + + private ContextService service; + + public ApiType getApiType() { + return apiType; + } + + public void setApiType(ApiType apiType) { + this.apiType = apiType; + } + + public ContextNamespace getNamespace() { + return namespace; + } + + public void setNamespace(ContextNamespace namespace) { + this.namespace = namespace; + } + + public ContextService getService() { + return service; + } + + public void setService(ContextService service) { + this.service = service; + } + + public String getContext() { + return context; + } + + public void setContext(String context) { + this.context = context; + } + + } + + public static class ContextNamespace { + private Position position; + + private String key; + + public Position getPosition() { + return position; + } + + public void setPosition(Position position) { + this.position = position; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + } + + public static class ContextService { + private Position position; + + private String key; + + public Position getPosition() { + return position; + } + + public void setPosition(Position position) { + this.position = position; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + } + + public static class ContextRoute { + private String path; + + private String pathMapping; + + private String method; + + private String service; + + private String host; + + private String namespace; + + private Map metadata; + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getPathMapping() { + return pathMapping; + } + + public void setPathMapping(String pathMapping) { + this.pathMapping = pathMapping; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getService() { + return service; + } + + public void setService(String service) { + this.service = service; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + } +} 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 new file mode 100644 index 0000000000..7277ce0373 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/Position.java @@ -0,0 +1,33 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.plugin.gateway.context; + +public enum Position { + /** + * Path position. + */ + PATH, + /** + * Query position. + */ + QUERY, + /** + * Header position. + */ + HEADER, +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json similarity index 100% rename from spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json rename to spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..1714707dc7 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.plugin.gateway.GatewayPluginAutoConfiguration diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/resources/application-test.yml b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/resources/application-test.yml similarity index 100% rename from spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/resources/application-test.yml rename to spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/test/resources/application-test.yml diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java deleted file mode 100644 index 324e0dc0f6..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway; - -import java.util.List; - -import com.tencent.cloud.plugin.gateway.staining.TrafficStainer; -import com.tencent.cloud.plugin.gateway.staining.TrafficStainingGatewayFilter; -import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingExecutor; -import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingProperties; -import com.tencent.cloud.plugin.gateway.staining.rule.RuleTrafficStainer; -import com.tencent.cloud.plugin.gateway.staining.rule.StainingRuleManager; -import com.tencent.cloud.polaris.config.ConditionalOnPolarisConfigEnabled; -import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; -import com.tencent.polaris.configuration.api.core.ConfigFileService; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Auto configuration for spring cloud gateway plugins. - * @author lepdou 2022-07-06 - */ -@Configuration -@ConditionalOnPolarisEnabled -@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.enabled", matchIfMissing = true) -public class SCGPluginsAutoConfiguration { - - @Configuration - @ConditionalOnProperty("spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled") - @ConditionalOnPolarisConfigEnabled - public static class RuleStainingPluginConfiguration { - - @Bean - public RuleStainingProperties ruleStainingProperties() { - return new RuleStainingProperties(); - } - - @Bean - public StainingRuleManager stainingRuleManager(RuleStainingProperties stainingProperties, ConfigFileService configFileService) { - return new StainingRuleManager(stainingProperties, configFileService); - } - - @Bean - public TrafficStainingGatewayFilter trafficStainingGatewayFilter(List trafficStainer) { - return new TrafficStainingGatewayFilter(trafficStainer); - } - - @Bean - public RuleStainingExecutor ruleStainingExecutor() { - return new RuleStainingExecutor(); - } - - @Bean - public RuleTrafficStainer ruleTrafficStainer(StainingRuleManager stainingRuleManager, RuleStainingExecutor ruleStainingExecutor) { - return new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor); - } - } -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.java deleted file mode 100644 index 184d52faee..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway.staining; - -import java.util.Comparator; -import java.util.HashMap; -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.cloud.common.util.JacksonUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -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.http.server.reactive.ServerHttpRequest; -import org.springframework.util.CollectionUtils; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER; - -/** - * Staining the request, and the stained labels will be passed to the link through transitive metadata. - * @author lepdou 2022-07-06 - */ -public class TrafficStainingGatewayFilter implements GlobalFilter, Ordered { - - private static final Logger LOGGER = LoggerFactory.getLogger(TrafficStainingGatewayFilter.class); - - private final List trafficStainers; - - public TrafficStainingGatewayFilter(List trafficStainers) { - if (!CollectionUtils.isEmpty(trafficStainers)) { - trafficStainers.sort(Comparator.comparingInt(Ordered::getOrder)); - } - this.trafficStainers = trafficStainers; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - if (CollectionUtils.isEmpty(trafficStainers)) { - return chain.filter(exchange); - } - - // 1. get stained labels from request - Map stainedLabels = getStainedLabels(exchange); - - if (CollectionUtils.isEmpty(stainedLabels)) { - return chain.filter(exchange); - } - - // 2. put stained labels to metadata context - ServerHttpRequest request = exchange.getRequest().mutate().headers((httpHeaders) -> { - MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT); - if (metadataContext == null) { - metadataContext = MetadataContextHolder.get(); - } - - Map oldTransitiveMetadata = metadataContext.getTransitiveMetadata(); - - // append new transitive metadata - Map newTransitiveMetadata = new HashMap<>(oldTransitiveMetadata); - newTransitiveMetadata.putAll(stainedLabels); - - metadataContext.setTransitiveMetadata(newTransitiveMetadata); - }).build(); - - return chain.filter(exchange.mutate().request(request).build()); - } - - Map getStainedLabels(ServerWebExchange exchange) { - Map stainedLabels = new HashMap<>(); - int size = trafficStainers.size(); - TrafficStainer stainer = null; - for (int i = size - 1; i >= 0; i--) { - try { - stainer = trafficStainers.get(i); - Map labels = stainer.apply(exchange); - if (!CollectionUtils.isEmpty(labels)) { - stainedLabels.putAll(labels); - } - } - catch (Exception e) { - if (stainer != null) { - LOGGER.error("[SCT] traffic stained error. stainer = {}", stainer.getClass().getName(), e); - } - } - } - LOGGER.debug("[SCT] traffic stained labels. {}", JacksonUtils.serialize2Json(stainedLabels)); - - return stainedLabels; - } - - @Override - public int getOrder() { - return ROUTE_TO_URL_FILTER_ORDER + 1; - } -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java deleted file mode 100644 index 90f08fbea7..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway.staining.rule; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.tencent.cloud.common.rule.Condition; -import com.tencent.cloud.common.rule.ConditionUtils; -import com.tencent.cloud.common.rule.KVPairUtils; -import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; - -import org.springframework.util.CollectionUtils; -import org.springframework.web.server.ServerWebExchange; - -/** - * Resolve labels from request by staining rule. - * @author lepdou 2022-07-11 - */ -public class RuleStainingExecutor { - - Map execute(ServerWebExchange exchange, StainingRule stainingRule) { - if (stainingRule == null) { - return Collections.emptyMap(); - } - - List rules = stainingRule.getRules(); - if (CollectionUtils.isEmpty(rules)) { - return Collections.emptyMap(); - } - - Map parsedLabels = new HashMap<>(); - - for (StainingRule.Rule rule : rules) { - List conditions = rule.getConditions(); - - Set keys = new HashSet<>(); - conditions.forEach(condition -> keys.add(condition.getKey())); - Map actualValues = SpringWebExpressionLabelUtils.resolve(exchange, keys); - - if (!ConditionUtils.match(actualValues, conditions)) { - continue; - } - - parsedLabels.putAll(KVPairUtils.toMap(rule.getLabels())); - } - - return parsedLabels; - } -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java deleted file mode 100644 index 782cd2ecd4..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway.staining.rule; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * The properties for rule staining. - * @author lepdou 2022-07-11 - */ -@ConfigurationProperties("spring.cloud.tencent.plugin.scg.staining.rule-staining") -public class RuleStainingProperties { - - @Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.namespace:${spring.cloud.tencent.namespace:default}}") - private String namespace; - - @Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.group:${spring.application.name:spring-cloud-gateway}}") - private String group; - - @Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.fileName:rule/staining.json}") - private String fileName; - - private boolean enabled = true; - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public String getFileName() { - return fileName; - } - - public void setFileName(String fileName) { - this.fileName = fileName; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.java deleted file mode 100644 index 4096e76386..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway.staining.rule; - -import java.util.Collections; -import java.util.Map; - -import com.tencent.cloud.plugin.gateway.staining.TrafficStainer; - -import org.springframework.web.server.ServerWebExchange; - -/** - * Staining the request by staining rules. - * @author lepdou 2022-07-06 - */ -public class RuleTrafficStainer implements TrafficStainer { - - private final StainingRuleManager stainingRuleManager; - private final RuleStainingExecutor ruleStainingExecutor; - - public RuleTrafficStainer(StainingRuleManager stainingRuleManager, RuleStainingExecutor ruleStainingExecutor) { - this.stainingRuleManager = stainingRuleManager; - this.ruleStainingExecutor = ruleStainingExecutor; - } - - @Override - public Map apply(ServerWebExchange exchange) { - StainingRule stainingRule = stainingRuleManager.getStainingRule(); - - if (stainingRule == null) { - return Collections.emptyMap(); - } - - return ruleStainingExecutor.execute(exchange, stainingRule); - } - - @Override - public int getOrder() { - return 0; - } -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java deleted file mode 100644 index 8a1df7a2bf..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway.staining.rule; - -import java.util.List; - -import com.tencent.cloud.common.rule.Condition; -import com.tencent.cloud.common.rule.KVPair; - -/** - * The rules for staining. - * @author lepdou 2022-07-07 - */ -public class StainingRule { - - private List rules; - - public List getRules() { - return rules; - } - - public void setRules(List rules) { - this.rules = rules; - } - - @Override - public String toString() { - return "StainingRule{" + - "rules=" + rules + - '}'; - } - - public static class Rule { - private List conditions; - private List labels; - - public List getConditions() { - return conditions; - } - - public void setConditions(List conditions) { - this.conditions = conditions; - } - - public List getLabels() { - return labels; - } - - public void setLabels(List labels) { - this.labels = labels; - } - - @Override - public String toString() { - return "Rule{" + - "conditions=" + conditions + - ", labels=" + labels + - '}'; - } - } - -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.java deleted file mode 100644 index 87823f540e..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway.staining.rule; - -import com.tencent.cloud.common.util.JacksonUtils; -import com.tencent.polaris.configuration.api.core.ConfigFile; -import com.tencent.polaris.configuration.api.core.ConfigFileService; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Fetch staining rule from polaris, and deserialize to {@link StainingRule}. - * @author lepdou 2022-07-07 - */ -public class StainingRuleManager { - private static final Logger LOGGER = LoggerFactory.getLogger(StainingRuleManager.class); - - private final RuleStainingProperties stainingProperties; - private final ConfigFileService configFileService; - - private StainingRule stainingRule; - - public StainingRuleManager(RuleStainingProperties stainingProperties, ConfigFileService configFileService) { - this.stainingProperties = stainingProperties; - this.configFileService = configFileService; - - initStainingRule(); - } - - private void initStainingRule() { - ConfigFile rulesFile = configFileService.getConfigFile(stainingProperties.getNamespace(), stainingProperties.getGroup(), - stainingProperties.getFileName()); - - rulesFile.addChangeListener(event -> { - LOGGER.info("[SCT] update scg staining rules. {}", event); - deserialize(event.getNewValue()); - }); - - String ruleJson = rulesFile.getContent(); - LOGGER.info("[SCT] init scg staining rules. {}", ruleJson); - - deserialize(ruleJson); - } - - private void deserialize(String ruleJsonStr) { - if (StringUtils.isBlank(ruleJsonStr)) { - stainingRule = null; - return; - } - - try { - stainingRule = JacksonUtils.deserialize(ruleJsonStr, StainingRule.class); - } - catch (Exception e) { - LOGGER.error("[SCT] deserialize staining rule error.", e); - throw e; - } - } - - public StainingRule getStainingRule() { - return stainingRule; - } -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 70c95961b9..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - com.tencent.cloud.plugin.gateway.SCGPluginsAutoConfiguration diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfigurationTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfigurationTest.java deleted file mode 100644 index 04f43644eb..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfigurationTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway; - -import com.tencent.cloud.plugin.gateway.staining.TrafficStainingGatewayFilter; -import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingExecutor; -import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingProperties; -import com.tencent.cloud.plugin.gateway.staining.rule.RuleTrafficStainer; -import com.tencent.cloud.plugin.gateway.staining.rule.StainingRuleManager; -import com.tencent.cloud.polaris.context.PolarisSDKContextManager; -import com.tencent.polaris.configuration.api.core.ConfigFileService; -import com.tencent.polaris.configuration.factory.ConfigFileServiceFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - -/** - * Test for {@link SCGPluginsAutoConfiguration}. - * @author derek.yi 2022-11-03 - */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = DEFINED_PORT, classes = SCGPluginsAutoConfigurationTest.TestApplication.class, - properties = {"server.port=48081", "spring.config.location = classpath:application-test.yml", - "spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled = true"}) -public class SCGPluginsAutoConfigurationTest { - - @Autowired - private ApplicationContext applicationContext; - - @Test - public void testAutoConfiguration() { - assertThat(applicationContext.getBeansOfType(RuleStainingProperties.class).size()).isEqualTo(1); - assertThat(applicationContext.getBeansOfType(StainingRuleManager.class).size()).isEqualTo(1); - assertThat(applicationContext.getBeansOfType(TrafficStainingGatewayFilter.class).size()).isEqualTo(1); - assertThat(applicationContext.getBeansOfType(RuleStainingExecutor.class).size()).isEqualTo(1); - assertThat(applicationContext.getBeansOfType(RuleTrafficStainer.class).size()).isEqualTo(1); - } - - @SpringBootApplication - public static class TestApplication { - - @Bean - public ConfigFileService configFileService(PolarisSDKContextManager polarisSDKContextManager) { - return ConfigFileServiceFactory.createConfigFileService(polarisSDKContextManager.getSDKContext()); - } - } -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilterTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilterTest.java deleted file mode 100644 index 44414bf7e3..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilterTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway.staining; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.metadata.MetadataContextHolder; -import com.tencent.cloud.common.util.ApplicationContextAwareUtils; -import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingExecutor; -import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingProperties; -import com.tencent.cloud.plugin.gateway.staining.rule.RuleTrafficStainer; -import com.tencent.cloud.plugin.gateway.staining.rule.StainingRuleManager; -import com.tencent.polaris.configuration.api.core.ConfigFile; -import com.tencent.polaris.configuration.api.core.ConfigFileService; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * Test for {@link TrafficStainingGatewayFilter}. - * @author lepdou 2022-07-12 - */ -@ExtendWith(MockitoExtension.class) -public class TrafficStainingGatewayFilterTest { - - private final String testNamespace = "testNamespace"; - private final String testGroup = "testGroup"; - private final String testFileName = "rule.json"; - @Mock - private GatewayFilterChain chain; - @Mock - private ServerWebExchange exchange; - @Mock - private ConfigFileService configFileService; - - @BeforeAll - static void beforeAll() { - Mockito.mockStatic(ApplicationContextAwareUtils.class); - when(ApplicationContextAwareUtils - .getProperties(any())).thenReturn("fooBar"); - } - - @Test - public void testNoneTrafficStainingImplement() { - TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(null); - - when(chain.filter(exchange)).thenReturn(Mono.empty()); - - filter.filter(exchange, chain); - - verify(chain).filter(exchange); - } - - @Test - public void testMultiStaining() { - TrafficStainer trafficStainer1 = Mockito.mock(TrafficStainer.class); - TrafficStainer trafficStainer2 = Mockito.mock(TrafficStainer.class); - - when(trafficStainer1.getOrder()).thenReturn(1); - when(trafficStainer2.getOrder()).thenReturn(2); - - Map labels1 = new HashMap<>(); - labels1.put("k1", "v1"); - labels1.put("k2", "v2"); - when(trafficStainer1.apply(exchange)).thenReturn(labels1); - - Map labels2 = new HashMap<>(); - labels2.put("k1", "v11"); - labels2.put("k3", "v3"); - when(trafficStainer2.apply(exchange)).thenReturn(labels2); - - TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(Arrays.asList(trafficStainer1, trafficStainer2)); - Map result = filter.getStainedLabels(exchange); - - assertThat(result).isNotEmpty(); - assertThat(result.get("k1")).isEqualTo("v1"); - assertThat(result.get("k2")).isEqualTo("v2"); - assertThat(result.get("k3")).isEqualTo("v3"); - } - - @Test - public void testNoTrafficStainers() { - MetadataContext metadataContext = new MetadataContext(); - MetadataContextHolder.set(metadataContext); - - TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(null); - filter.filter(exchange, chain); - Map map = metadataContext.getTransitiveMetadata(); - assertThat(map).isEmpty(); - } - - @Test - public void testWithTrafficStainers() { - MetadataContext metadataContext = new MetadataContext(); - MetadataContextHolder.set(metadataContext); - - RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); - ruleStainingProperties.setNamespace(testNamespace); - ruleStainingProperties.setGroup(testGroup); - ruleStainingProperties.setFileName(testFileName); - - ConfigFile configFile = Mockito.mock(ConfigFile.class); - when(configFile.getContent()).thenReturn("{\n" - + " \"rules\":[\n" - + " {\n" - + " \"conditions\":[\n" - + " {\n" - + " \"key\":\"${http.query.uid}\",\n" - + " \"values\":[\"1000\"],\n" - + " \"operation\":\"EQUALS\"\n" - + " }\n" - + " ],\n" - + " \"labels\":[\n" - + " {\n" - + " \"key\":\"env\",\n" - + " \"value\":\"blue\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"); - when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); - - StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService); - RuleStainingExecutor ruleStainingExecutor = new RuleStainingExecutor(); - RuleTrafficStainer ruleTrafficStainer = new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor); - - TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(Collections.singletonList(ruleTrafficStainer)); - - MockServerHttpRequest request = MockServerHttpRequest.get("/users") - .queryParam("uid", "1000").build(); - MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); - - filter.filter(exchange, chain); - Map map = metadataContext.getTransitiveMetadata(); - assertThat(map).isNotNull(); - assertThat(map.size()).isEqualTo(1); - assertThat(map.get("env")).isEqualTo("blue"); - } -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java deleted file mode 100644 index cff02243a8..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway.staining.rule; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -import com.tencent.cloud.common.rule.Condition; -import com.tencent.cloud.common.rule.KVPair; -import com.tencent.cloud.common.rule.Operation; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test for {@link RuleStainingExecutor}. - * @author lepdou 2022-07-12 - */ -@ExtendWith(MockitoExtension.class) -public class RuleStainingExecutorTest { - - @Test - public void testMatchCondition() { - Condition condition1 = new Condition(); - condition1.setKey("${http.header.uid}"); - condition1.setOperation(Operation.EQUALS.toString()); - condition1.setValues(Collections.singletonList("1000")); - - Condition condition2 = new Condition(); - condition2.setKey("${http.query.source}"); - condition2.setOperation(Operation.IN.toString()); - condition2.setValues(Collections.singletonList("wx")); - - StainingRule.Rule rule = new StainingRule.Rule(); - rule.setConditions(Arrays.asList(condition1, condition2)); - - KVPair kvPair = new KVPair(); - kvPair.setKey("env"); - kvPair.setValue("blue"); - rule.setLabels(Collections.singletonList(kvPair)); - - StainingRule stainingRule = new StainingRule(); - stainingRule.setRules(Collections.singletonList(rule)); - - MockServerHttpRequest request = MockServerHttpRequest.get("/users") - .queryParam("source", "wx") - .header("uid", "1000").build(); - MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); - - RuleStainingExecutor executor = new RuleStainingExecutor(); - - Map stainedLabels = executor.execute(exchange, stainingRule); - - assertThat(stainedLabels).isNotNull(); - assertThat(stainedLabels.size()).isEqualTo(1); - assertThat(stainedLabels.get("env")).isEqualTo("blue"); - } - - @Test - public void testNotMatchCondition() { - Condition condition1 = new Condition(); - condition1.setKey("${http.header.uid}"); - condition1.setOperation(Operation.EQUALS.toString()); - condition1.setValues(Collections.singletonList("1000")); - - Condition condition2 = new Condition(); - condition2.setKey("${http.query.source}"); - condition2.setOperation(Operation.IN.toString()); - condition2.setValues(Collections.singletonList("wx")); - - StainingRule.Rule rule = new StainingRule.Rule(); - rule.setConditions(Arrays.asList(condition1, condition2)); - - KVPair kvPair = new KVPair(); - kvPair.setKey("env"); - kvPair.setValue("blue"); - rule.setLabels(Collections.singletonList(kvPair)); - - StainingRule stainingRule = new StainingRule(); - stainingRule.setRules(Collections.singletonList(rule)); - - MockServerHttpRequest request = MockServerHttpRequest.get("/users") - .queryParam("source", "wx") - .header("uid", "10001").build(); - MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); - - RuleStainingExecutor executor = new RuleStainingExecutor(); - - Map stainedLabels = executor.execute(exchange, stainingRule); - - assertThat(stainedLabels).isNotNull(); - assertThat(stainedLabels.size()).isEqualTo(0); - } - - @Test - public void testMatchTwoRulesAndNotMatchOneRule() { - Condition condition1 = new Condition(); - condition1.setKey("${http.header.uid}"); - condition1.setOperation(Operation.EQUALS.toString()); - condition1.setValues(Collections.singletonList("1000")); - - Condition condition2 = new Condition(); - condition2.setKey("${http.query.source}"); - condition2.setOperation(Operation.IN.toString()); - condition2.setValues(Collections.singletonList("wx")); - - // rule1 matched - StainingRule.Rule rule1 = new StainingRule.Rule(); - rule1.setConditions(Arrays.asList(condition1, condition2)); - - KVPair kvPair = new KVPair(); - kvPair.setKey("env"); - kvPair.setValue("blue"); - rule1.setLabels(Collections.singletonList(kvPair)); - - // rule2 matched - StainingRule.Rule rule2 = new StainingRule.Rule(); - rule2.setConditions(Collections.singletonList(condition1)); - - KVPair kvPair2 = new KVPair(); - kvPair2.setKey("label1"); - kvPair2.setValue("value1"); - KVPair kvPair3 = new KVPair(); - kvPair3.setKey("label2"); - kvPair3.setValue("value2"); - rule2.setLabels(Arrays.asList(kvPair2, kvPair3)); - - // rule3 not matched - Condition condition3 = new Condition(); - condition3.setKey("${http.query.type}"); - condition3.setOperation(Operation.IN.toString()); - condition3.setValues(Collections.singletonList("wx")); - - StainingRule.Rule rule3 = new StainingRule.Rule(); - rule3.setConditions(Collections.singletonList(condition3)); - - KVPair kvPair4 = new KVPair(); - kvPair4.setKey("label3"); - kvPair4.setValue("value3"); - rule3.setLabels(Collections.singletonList(kvPair4)); - - StainingRule stainingRule = new StainingRule(); - stainingRule.setRules(Arrays.asList(rule1, rule2, rule3)); - - MockServerHttpRequest request = MockServerHttpRequest.get("/users") - .queryParam("source", "wx") - .header("uid", "1000").build(); - MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); - - RuleStainingExecutor executor = new RuleStainingExecutor(); - - Map stainedLabels = executor.execute(exchange, stainingRule); - - assertThat(stainedLabels).isNotNull(); - assertThat(stainedLabels.size()).isEqualTo(3); - assertThat(stainedLabels.get("env")).isEqualTo("blue"); - assertThat(stainedLabels.get("label1")).isEqualTo("value1"); - assertThat(stainedLabels.get("label2")).isEqualTo("value2"); - } - - @Test - public void testNoStainingRule() { - RuleStainingExecutor executor = new RuleStainingExecutor(); - assertThat(executor.execute(null, null)).isEmpty(); - assertThat(executor.execute(null, new StainingRule())).isEmpty(); - } -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainerTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainerTest.java deleted file mode 100644 index d65a913a02..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainerTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway.staining.rule; - -import java.util.Map; - -import com.tencent.polaris.configuration.api.core.ConfigFile; -import com.tencent.polaris.configuration.api.core.ConfigFileService; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -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.mockito.Mockito.when; - -/** - * Test for {@link RuleTrafficStainer}. - * @author derek.yi 2022-11-03 - */ -@ExtendWith(MockitoExtension.class) -public class RuleTrafficStainerTest { - - private final String testNamespace = "testNamespace"; - private final String testGroup = "testGroup"; - private final String testFileName = "rule.json"; - @Mock - private ConfigFileService configFileService; - - @Test - public void testNoStainingRule() { - RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); - ruleStainingProperties.setNamespace(testNamespace); - ruleStainingProperties.setGroup(testGroup); - ruleStainingProperties.setFileName(testFileName); - - ConfigFile configFile = Mockito.mock(ConfigFile.class); - when(configFile.getContent()).thenReturn(""); - when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); - - StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService); - RuleStainingExecutor ruleStainingExecutor = new RuleStainingExecutor(); - RuleTrafficStainer ruleTrafficStainer = new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor); - Map map = ruleTrafficStainer.apply(null); - assertThat(map).isEmpty(); - } - - @Test - public void testWithStainingRule() { - RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); - ruleStainingProperties.setNamespace(testNamespace); - ruleStainingProperties.setGroup(testGroup); - ruleStainingProperties.setFileName(testFileName); - - ConfigFile configFile = Mockito.mock(ConfigFile.class); - when(configFile.getContent()).thenReturn("{\n" - + " \"rules\":[\n" - + " {\n" - + " \"conditions\":[\n" - + " {\n" - + " \"key\":\"${http.query.uid}\",\n" - + " \"values\":[\"1000\"],\n" - + " \"operation\":\"EQUALS\"\n" - + " }\n" - + " ],\n" - + " \"labels\":[\n" - + " {\n" - + " \"key\":\"env\",\n" - + " \"value\":\"blue\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"); - when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); - - StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService); - RuleStainingExecutor ruleStainingExecutor = new RuleStainingExecutor(); - RuleTrafficStainer ruleTrafficStainer = new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor); - - MockServerHttpRequest request = MockServerHttpRequest.get("/users") - .queryParam("uid", "1000").build(); - MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); - - Map map = ruleTrafficStainer.apply(exchange); - assertThat(map).isNotNull(); - assertThat(map.size()).isEqualTo(1); - assertThat(map.get("env")).isEqualTo("blue"); - } -} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java deleted file mode 100644 index d8d90a5f7f..0000000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making spring-cloud-tencent available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.tencent.cloud.plugin.gateway.staining.rule; - -import com.tencent.polaris.configuration.api.core.ConfigFile; -import com.tencent.polaris.configuration.api.core.ConfigFileService; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.mockito.Mockito.when; - -/** - * Test for {@link StainingRuleManager}. - * @author lepdou 2022-07-12 - */ -@ExtendWith(MockitoExtension.class) -public class StainingRuleManagerTest { - - private final String testNamespace = "testNamespace"; - private final String testGroup = "testGroup"; - private final String testFileName = "rule.json"; - @Mock - private ConfigFileService configFileService; - - @Test - public void testNormalRule() { - RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); - ruleStainingProperties.setNamespace(testNamespace); - ruleStainingProperties.setGroup(testGroup); - ruleStainingProperties.setFileName(testFileName); - - ConfigFile configFile = Mockito.mock(ConfigFile.class); - when(configFile.getContent()).thenReturn("{\n" - + " \"rules\":[\n" - + " {\n" - + " \"conditions\":[\n" - + " {\n" - + " \"key\":\"${http.query.uid}\",\n" - + " \"values\":[\"1000\"],\n" - + " \"operation\":\"EQUALS\"\n" - + " }\n" - + " ],\n" - + " \"labels\":[\n" - + " {\n" - + " \"key\":\"env\",\n" - + " \"value\":\"blue\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"); - when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); - - StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService); - - StainingRule stainingRule = stainingRuleManager.getStainingRule(); - - assertThat(stainingRule).isNotNull(); - assertThat(stainingRule.getRules().size()).isEqualTo(1); - StainingRule.Rule rule = stainingRule.getRules().get(0); - assertThat(rule.getConditions().size()).isEqualTo(1); - assertThat(rule.getLabels().size()).isEqualTo(1); - } - - @Test - public void testWrongRule() { - assertThatCode(() -> { - RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); - ruleStainingProperties.setNamespace(testNamespace); - ruleStainingProperties.setGroup(testGroup); - ruleStainingProperties.setFileName(testFileName); - - ConfigFile configFile = Mockito.mock(ConfigFile.class); - when(configFile.getContent()).thenReturn("{\n" - + " \"rules\":[\n" - + " {\n" - + " \"conditionsxxxx\":[\n" - + " {\n" - + " \"key\":\"${http.query.uid}\",\n" - + " \"values\":[\"1000\"],\n" - + " \"operation\":\"EQUALS\"\n" - + " }\n" - + " ],\n" - + " \"labels\":[\n" - + " {\n" - + " \"key\":\"env\",\n" - + " \"value\":\"blue\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"); - when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); - - new StainingRuleManager(ruleStainingProperties, configFileService); - }).isInstanceOf(RuntimeException.class); - } - - @Test - public void testEmptyRule() { - RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); - ruleStainingProperties.setNamespace(testNamespace); - ruleStainingProperties.setGroup(testGroup); - ruleStainingProperties.setFileName(testFileName); - - ConfigFile configFile = Mockito.mock(ConfigFile.class); - when(configFile.getContent()).thenReturn(null); - when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); - - StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService); - assertThat(stainingRuleManager.getStainingRule()).isNull(); - } -} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/feign/EnhancedFeignClient.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/feign/EnhancedFeignClient.java index ad60f858d1..98f3d7645e 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/feign/EnhancedFeignClient.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/feign/EnhancedFeignClient.java @@ -97,6 +97,7 @@ public Response execute(Request request, Options options) throws IOException { try { // Run pre enhanced plugins. pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext); + startMillis = System.currentTimeMillis(); Response response = delegate.execute(request, options); enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java index b30c7835ae..f2bed62c0d 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java @@ -79,30 +79,12 @@ public ClientHttpResponse intercept(HttpRequest request, String serviceId, enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance()); - - // Run pre enhanced plugins. + long startMillis = System.currentTimeMillis(); try { + // Run pre enhanced plugins. pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext); - } - catch (CallAbortedException callAbortedException) { - MetadataObjectValue fallbackResponseValue = MetadataContextHolder.get(). - getMetadataContainer(MetadataType.APPLICATION, true). - getMetadataValue(ContextConstant.CircuitBreaker.CIRCUIT_BREAKER_FALLBACK_HTTP_RESPONSE); - - boolean existFallback = Optional.ofNullable(fallbackResponseValue). - map(MetadataObjectValue::getObjectValue).map(Optional::isPresent).orElse(false); - - if (existFallback) { - Object fallbackResponse = fallbackResponseValue.getObjectValue().orElse(null); - if (fallbackResponse instanceof ClientHttpResponse) { - return (ClientHttpResponse) fallbackResponse; - } - } - throw callAbortedException; - } + startMillis = System.currentTimeMillis(); - long startMillis = System.currentTimeMillis(); - try { ClientHttpResponse response = delegate.execute(serviceId, loadBalancerRequest); // get target instance after execute enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get() @@ -121,6 +103,22 @@ public ClientHttpResponse intercept(HttpRequest request, String serviceId, return response; } + catch (CallAbortedException callAbortedException) { + MetadataObjectValue fallbackResponseValue = MetadataContextHolder.get(). + getMetadataContainer(MetadataType.APPLICATION, true). + getMetadataValue(ContextConstant.CircuitBreaker.CIRCUIT_BREAKER_FALLBACK_HTTP_RESPONSE); + + boolean existFallback = Optional.ofNullable(fallbackResponseValue). + map(MetadataObjectValue::getObjectValue).map(Optional::isPresent).orElse(false); + + if (existFallback) { + Object fallbackResponse = fallbackResponseValue.getObjectValue().orElse(null); + if (fallbackResponse instanceof ClientHttpResponse) { + return (ClientHttpResponse) fallbackResponse; + } + } + throw callAbortedException; + } catch (IOException e) { enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); enhancedPluginContext.setThrowable(e); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java index e26ccc1e01..0548842f45 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java @@ -21,7 +21,10 @@ import java.nio.charset.StandardCharsets; import java.util.Optional; +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.rpc.enhancement.plugin.EnhancedPluginContext; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; @@ -67,6 +70,19 @@ public Mono filter(ServerWebExchange originExchange, GatewayFilterChain ch String serviceId = Optional.ofNullable(serviceInstanceResponse).map(Response::getServer). map(ServiceInstance::getServiceId).orElse(null); + MetadataContext metadataContext = (MetadataContext) originExchange.getAttributes().get( + MetadataConstant.HeaderName.METADATA_CONTEXT); + if (metadataContext != null) { + MetadataContextHolder.set(metadataContext); + } + else { + metadataContext = MetadataContextHolder.get(); + } + + String governanceNamespace = metadataContext.getContext(MetadataContext.FRAGMENT_APPLICATION_NONE, + MetadataConstant.POLARIS_TARGET_NAMESPACE, MetadataContext.LOCAL_NAMESPACE); + + EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext(); EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder() @@ -74,6 +90,7 @@ public Mono filter(ServerWebExchange originExchange, GatewayFilterChain ch .httpMethod(originExchange.getRequest().getMethod()) .url(originExchange.getRequest().getURI()) .serviceUrl(getServiceUri(originExchange, serviceId)) + .governanceNamespace(governanceNamespace) .build(); enhancedPluginContext.setRequest(enhancedRequestContext); enhancedPluginContext.setOriginRequest(originExchange); @@ -83,6 +100,8 @@ public Mono filter(ServerWebExchange originExchange, GatewayFilterChain ch pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext); } catch (CallAbortedException e) { + // Run finally enhanced plugins. + pluginRunner.run(EnhancedPluginType.Client.FINALLY, enhancedPluginContext); if (e.getFallbackInfo() == null) { throw e; } @@ -106,7 +125,8 @@ public Mono filter(ServerWebExchange originExchange, GatewayFilterChain ch URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); enhancedPluginContext.getRequest().setUrl(uri); if (uri != null) { - if (route != null && route.getUri().getScheme().contains("lb") && StringUtils.isNotEmpty(serviceId)) { + if (route != null && route.getUri().getScheme() + .contains("lb") && StringUtils.isNotEmpty(serviceId)) { DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); serviceInstance.setServiceId(serviceId); serviceInstance.setHost(uri.getHost()); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java index 73008f51ba..13a0bb554a 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java @@ -74,6 +74,9 @@ public Mono filter(ClientRequest originRequest, ExchangeFunction pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext); } catch (CallAbortedException e) { + // Run finally enhanced plugins. + pluginRunner.run(EnhancedPluginType.Client.FINALLY, enhancedPluginContext); + if (e.getFallbackInfo() == null) { throw e; } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedRequestContext.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedRequestContext.java index e176564874..8276e377ea 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedRequestContext.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedRequestContext.java @@ -37,6 +37,8 @@ public class EnhancedRequestContext { private URI serviceUrl; + private String governanceNamespace; + public HttpMethod getHttpMethod() { return httpMethod; } @@ -69,6 +71,14 @@ public void setServiceUrl(URI serviceUrl) { this.serviceUrl = serviceUrl; } + public String getGovernanceNamespace() { + return governanceNamespace; + } + + public void setGovernanceNamespace(String governanceNamespace) { + this.governanceNamespace = governanceNamespace; + } + public static EnhancedContextRequestBuilder builder() { return new EnhancedContextRequestBuilder(); } @@ -87,8 +97,8 @@ public static final class EnhancedContextRequestBuilder { private HttpMethod httpMethod; private HttpHeaders httpHeaders; private URI url; - private URI serviceUrl; + private String governanceNamespace; private EnhancedContextRequestBuilder() { } @@ -113,12 +123,18 @@ public EnhancedContextRequestBuilder serviceUrl(URI serviceUrl) { return this; } + public EnhancedContextRequestBuilder governanceNamespace(String governanceNamespace) { + this.governanceNamespace = governanceNamespace; + return this; + } + public EnhancedRequestContext build() { EnhancedRequestContext enhancedRequestContext = new EnhancedRequestContext(); enhancedRequestContext.httpMethod = this.httpMethod; enhancedRequestContext.url = this.url; enhancedRequestContext.httpHeaders = this.httpHeaders; enhancedRequestContext.serviceUrl = this.serviceUrl; + enhancedRequestContext.governanceNamespace = this.governanceNamespace; return enhancedRequestContext; } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PolarisEnhancedPluginUtils.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PolarisEnhancedPluginUtils.java index 779f4bb005..1b0451fcf5 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PolarisEnhancedPluginUtils.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PolarisEnhancedPluginUtils.java @@ -33,8 +33,10 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.common.util.RequestLabelUtils; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; @@ -133,7 +135,11 @@ public static ServiceCallResult createServiceCallResult(String callerHost, public static ResourceStat createInstanceResourceStat( @Nullable String calleeServiceName, @Nullable String calleeHost, @Nullable Integer calleePort, URI uri, @Nullable Integer statusCode, long delay, @Nullable Throwable exception) { - ServiceKey calleeServiceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, StringUtils.isBlank(calleeServiceName) ? uri.getHost() : calleeServiceName); + + String governanceNamespace = MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_APPLICATION_NONE, + MetadataConstant.POLARIS_TARGET_NAMESPACE, MetadataContext.LOCAL_NAMESPACE); + + ServiceKey calleeServiceKey = new ServiceKey(governanceNamespace, StringUtils.isBlank(calleeServiceName) ? uri.getHost() : calleeServiceName); ServiceKey callerServiceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE); Resource resource = new InstanceResource( calleeServiceKey, diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/transformer/PolarisInstanceTransformer.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/transformer/PolarisInstanceTransformer.java index 2adcd1b403..56d6f058dd 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/transformer/PolarisInstanceTransformer.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/transformer/PolarisInstanceTransformer.java @@ -17,6 +17,9 @@ package com.tencent.cloud.rpc.enhancement.transformer; +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.pojo.PolarisServiceInstance; import com.tencent.polaris.api.pojo.DefaultInstance; import com.tencent.polaris.api.utils.CollectionUtils; @@ -42,6 +45,11 @@ public void transformCustom(DefaultInstance instance, ServiceInstance serviceIns if (CollectionUtils.isNotEmpty(polarisServiceInstance.getServiceMetadata())) { instance.setServiceMetadata(polarisServiceInstance.getServiceMetadata()); } + + String namespace = MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_APPLICATION_NONE, + MetadataConstant.POLARIS_TARGET_NAMESPACE, instance.getNamespace()); + + instance.setNamespace(namespace); } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java index dcd1a8a48e..21a3f3611d 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java @@ -173,7 +173,7 @@ void testInterceptWithFallback() throws IOException { when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance); doThrow(abortedException) .when(pluginRunner) - .run(any(), any()); + .run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class)); Object fallbackResponse = new MockClientHttpResponse(); MetadataContextHolder.get().getMetadataContainer(MetadataType.APPLICATION, true).