From d72cd884adca884ff6c43bc88c86f73a6c7a1d3d Mon Sep 17 00:00:00 2001 From: lepdou Date: Fri, 20 May 2022 14:43:43 +0800 Subject: [PATCH] feature: support router expression label --- CHANGELOG.md | 3 +- .../pom.xml | 7 + .../router/RouterRuleLabelResolver.java | 75 ++++++++ .../config/RouterAutoConfiguration.java | 12 +- .../feign/FeignExpressionLabelUtils.java | 81 ++++++++ .../feign/PolarisFeignLoadBalancer.java | 10 +- .../router/feign/RouterLabelInterceptor.java | 39 +++- .../PolarisLoadBalancerBeanPostProcessor.java | 4 +- .../PolarisLoadBalancerInterceptor.java | 37 +++- spring-cloud-tencent-commons/pom.xml | 12 ++ .../common/util/ExpressionLabelUtils.java | 178 +++++++++++++++--- .../polaris/context/ServiceRuleManager.java | 11 +- 12 files changed, 430 insertions(+), 39 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3f17d2d..0fd331294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,4 +3,5 @@ - [Feature: Support parse ratelimit rule expression labels.](https://github.com/Tencent/spring-cloud-tencent/pull/183) -- [fix:Router support request label.](https://github.com/Tencent/spring-cloud-tencent/pull/165) \ No newline at end of file +- [Feature: Router support request label.](https://github.com/Tencent/spring-cloud-tencent/pull/165) +- [Feature: Support router expression label](https://github.com/Tencent/spring-cloud-tencent/pull/190) diff --git a/spring-cloud-starter-tencent-polaris-router/pom.xml b/spring-cloud-starter-tencent-polaris-router/pom.xml index 94a69d9bc..6f0fa315d 100644 --- a/spring-cloud-starter-tencent-polaris-router/pom.xml +++ b/spring-cloud-starter-tencent-polaris-router/pom.xml @@ -33,6 +33,13 @@ spring-cloud-starter-openfeign true + + + org.springframework.boot + spring-boot-starter-web + true + + diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java new file mode 100644 index 000000000..5ab6e549f --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java @@ -0,0 +1,75 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.router; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.polaris.context.ServiceRuleManager; +import com.tencent.polaris.client.pb.ModelProto; +import com.tencent.polaris.client.pb.RoutingProto; + +import org.springframework.util.CollectionUtils; + +/** + * Resolve label expressions from routing rules. + * @author lepdou 2022-05-19 + */ +public class RouterRuleLabelResolver { + + private final ServiceRuleManager serviceRuleManager; + + public RouterRuleLabelResolver(ServiceRuleManager serviceRuleManager) { + this.serviceRuleManager = serviceRuleManager; + } + + public Set getExpressionLabelKeys(String namespace, String sourceService, String dstService) { + List rules = serviceRuleManager.getServiceRouterRule(namespace, sourceService, dstService); + + if (CollectionUtils.isEmpty(rules)) { + return Collections.emptySet(); + } + + Set expressionLabels = new HashSet<>(); + + for (RoutingProto.Route rule : rules) { + List sources = rule.getSourcesList(); + if (CollectionUtils.isEmpty(sources)) { + continue; + } + for (RoutingProto.Source source : sources) { + Map labels = source.getMetadataMap(); + if (CollectionUtils.isEmpty(labels)) { + continue; + } + for (String labelKey : labels.keySet()) { + if (ExpressionLabelUtils.isExpressionLabel(labelKey)) { + expressionLabels.add(labelKey); + } + } + } + } + + return expressionLabels; + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java index 950638406..5065b09b5 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java @@ -19,6 +19,8 @@ package com.tencent.cloud.polaris.router.config; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.polaris.context.ServiceRuleManager; +import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.feign.PolarisCachingSpringLoadBalanceFactory; import com.tencent.cloud.polaris.router.feign.RouterLabelInterceptor; import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerBeanPostProcessor; @@ -44,8 +46,9 @@ public class RouterAutoConfiguration { @Bean public RouterLabelInterceptor routerLabelInterceptor(@Nullable RouterLabelResolver resolver, - MetadataLocalProperties metadataLocalProperties) { - return new RouterLabelInterceptor(resolver, metadataLocalProperties); + MetadataLocalProperties metadataLocalProperties, + RouterRuleLabelResolver routerRuleLabelResolver) { + return new RouterLabelInterceptor(resolver, metadataLocalProperties, routerRuleLabelResolver); } @Bean @@ -58,4 +61,9 @@ public class RouterAutoConfiguration { public PolarisLoadBalancerBeanPostProcessor polarisLoadBalancerBeanPostProcessor() { return new PolarisLoadBalancerBeanPostProcessor(); } + + @Bean + public RouterRuleLabelResolver routerRuleLabelResolver(ServiceRuleManager serviceRuleManager) { + return new RouterRuleLabelResolver(serviceRuleManager); + } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java new file mode 100644 index 000000000..cf4408d37 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java @@ -0,0 +1,81 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.router.feign; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import com.tencent.cloud.common.util.ExpressionLabelUtils; +import feign.RequestTemplate; +import org.apache.commons.lang.StringUtils; + +import org.springframework.util.CollectionUtils; + +/** + * Resolve rule expression label from feign request. + * @author lepdou 2022-05-20 + */ +public class FeignExpressionLabelUtils { + + public static Map resolve(RequestTemplate request, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + Map labels = new HashMap<>(); + + for (String labelKey : labelKeys) { + if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) { + String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey); + if (StringUtils.isBlank(headerKey)) { + continue; + } + labels.put(labelKey, getHeaderValue(request, headerKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) { + String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey); + if (StringUtils.isBlank(queryKey)) { + continue; + } + labels.put(labelKey, getQueryValue(request, queryKey)); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) { + labels.put(labelKey, request.method()); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) { + labels.put(labelKey, request.request().url()); + } + } + + return labels; + } + + public static String getHeaderValue(RequestTemplate request, String key) { + Map> headers = request.headers(); + return ExpressionLabelUtils.getFirstValue(headers, key); + + } + + public static String getQueryValue(RequestTemplate request, String key) { + return ExpressionLabelUtils.getFirstValue(request.queries(), key); + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java index d9ac98b04..067332dd6 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java @@ -19,11 +19,13 @@ package com.tencent.cloud.polaris.router.feign; import java.util.Collection; +import java.util.HashMap; import java.util.Map; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.reactive.LoadBalancerCommand; +import com.tencent.cloud.common.util.ExpressionLabelUtils; import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.polaris.router.PolarisRouterContext; import com.tencent.cloud.polaris.router.RouterConstants; @@ -59,7 +61,13 @@ public class PolarisFeignLoadBalancer extends FeignLoadBalancer { labelHeaderValues.forEach(labelHeaderValue -> { Map labels = JacksonUtils.deserialize2Map(labelHeaderValue); if (!CollectionUtils.isEmpty(labels)) { - routerContext.setLabels(labels); + Map unescapeLabels = new HashMap<>(labels.size()); + for (Map.Entry entry : labels.entrySet()) { + String escapedKey = ExpressionLabelUtils.unescape(entry.getKey()); + String escapedValue = ExpressionLabelUtils.unescape(entry.getValue()); + unescapeLabels.put(escapedKey, escapedValue); + } + routerContext.setLabels(unescapeLabels); } }); diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelInterceptor.java index 47d4cc1fe..48c3fb667 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelInterceptor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelInterceptor.java @@ -18,14 +18,18 @@ package com.tencent.cloud.polaris.router.feign; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.util.ExpressionLabelUtils; import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.polaris.router.RouterConstants; +import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.RouterLabelResolver; import feign.RequestInterceptor; import feign.RequestTemplate; @@ -45,11 +49,14 @@ public class RouterLabelInterceptor implements RequestInterceptor, Ordered { private final RouterLabelResolver resolver; private final MetadataLocalProperties metadataLocalProperties; + private final RouterRuleLabelResolver routerRuleLabelResolver; public RouterLabelInterceptor(RouterLabelResolver resolver, - MetadataLocalProperties metadataLocalProperties) { + MetadataLocalProperties metadataLocalProperties, + RouterRuleLabelResolver routerRuleLabelResolver) { this.resolver = resolver; this.metadataLocalProperties = metadataLocalProperties; + this.routerRuleLabelResolver = routerRuleLabelResolver; } @Override @@ -79,10 +86,38 @@ public class RouterLabelInterceptor implements RequestInterceptor, Ordered { } } + // labels from rule expression + String peerServiceName = requestTemplate.feignTarget().name(); + Map ruleExpressionLabels = getRuleExpressionLabels(requestTemplate, peerServiceName); + labels.putAll(ruleExpressionLabels); + + //local service labels labels.putAll(metadataLocalProperties.getContent()); + // Because when the label is placed in RequestTemplate.header, + // RequestTemplate will parse the header according to the regular, which conflicts with the expression. + // Avoid conflicts by escaping. + Map escapeLabels = new HashMap<>(labels.size()); + for (Map.Entry entry : labels.entrySet()) { + String escapedKey = ExpressionLabelUtils.escape(entry.getKey()); + String escapedValue = ExpressionLabelUtils.escape(entry.getValue()); + escapeLabels.put(escapedKey, escapedValue); + } + // pass label by header - requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, JacksonUtils.serialize2Json(labels)); + requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, JacksonUtils.serialize2Json(escapeLabels)); + } + + private Map getRuleExpressionLabels(RequestTemplate requestTemplate, String peerService) { + Set labelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE, + MetadataContext.LOCAL_SERVICE, peerService); + + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + return FeignExpressionLabelUtils.resolve(requestTemplate, labelKeys); } + } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerBeanPostProcessor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerBeanPostProcessor.java index bdee51767..6fe192003 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerBeanPostProcessor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerBeanPostProcessor.java @@ -19,6 +19,7 @@ package com.tencent.cloud.polaris.router.resttemplate; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.RouterLabelResolver; import org.springframework.beans.BeansException; @@ -51,9 +52,10 @@ public class PolarisLoadBalancerBeanPostProcessor implements BeanPostProcessor, LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class); RouterLabelResolver routerLabelResolver = this.factory.getBean(RouterLabelResolver.class); MetadataLocalProperties metadataLocalProperties = this.factory.getBean(MetadataLocalProperties.class); + RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class); return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory, - routerLabelResolver, metadataLocalProperties); + routerLabelResolver, metadataLocalProperties, routerRuleLabelResolver); } return bean; } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java index 53c2a3401..2e3e691f4 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java @@ -20,13 +20,17 @@ package com.tencent.cloud.polaris.router.resttemplate; import java.io.IOException; import java.net.URI; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.util.ExpressionLabelUtils; import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.RouterLabelResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,18 +58,21 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { private final LoadBalancerRequestFactory requestFactory; private final RouterLabelResolver resolver; private final MetadataLocalProperties metadataLocalProperties; + private final RouterRuleLabelResolver routerRuleLabelResolver; private final boolean isRibbonLoadBalanceClient; public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory, RouterLabelResolver resolver, - MetadataLocalProperties metadataLocalProperties) { + MetadataLocalProperties metadataLocalProperties, + RouterRuleLabelResolver routerRuleLabelResolver) { super(loadBalancer, requestFactory); this.loadBalancer = loadBalancer; this.requestFactory = requestFactory; this.resolver = resolver; this.metadataLocalProperties = metadataLocalProperties; + this.routerRuleLabelResolver = routerRuleLabelResolver; this.isRibbonLoadBalanceClient = loadBalancer instanceof RibbonLoadBalancerClient; } @@ -73,22 +80,22 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); - String serviceName = originalUri.getHost(); - Assert.state(serviceName != null, + String peerServiceName = originalUri.getHost(); + Assert.state(peerServiceName != null, "Request URI does not contain a valid hostname: " + originalUri); if (isRibbonLoadBalanceClient) { - PolarisRouterContext routerContext = genRouterContext(request, body); + PolarisRouterContext routerContext = genRouterContext(request, body, peerServiceName); - return ((RibbonLoadBalancerClient) loadBalancer).execute(serviceName, + return ((RibbonLoadBalancerClient) loadBalancer).execute(peerServiceName, this.requestFactory.createRequest(request, body, execution), routerContext); } - return this.loadBalancer.execute(serviceName, + return this.loadBalancer.execute(peerServiceName, this.requestFactory.createRequest(request, body, execution)); } - private PolarisRouterContext genRouterContext(HttpRequest request, byte[] body) { + private PolarisRouterContext genRouterContext(HttpRequest request, byte[] body, String peerServiceName) { Map labels = new HashMap<>(); // labels from downstream @@ -109,6 +116,11 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { } } + Map ruleExpressionLabels = getExpressionLabels(request, peerServiceName); + if (!CollectionUtils.isEmpty(ruleExpressionLabels)) { + labels.putAll(ruleExpressionLabels); + } + //local service labels labels.putAll(metadataLocalProperties.getContent()); @@ -117,4 +129,15 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { return routerContext; } + + private Map getExpressionLabels(HttpRequest request, String peerServiceName) { + Set labelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE, + MetadataContext.LOCAL_SERVICE, peerServiceName); + + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + return ExpressionLabelUtils.resolve(request, labelKeys); + } } diff --git a/spring-cloud-tencent-commons/pom.xml b/spring-cloud-tencent-commons/pom.xml index b9b853c2e..eae677526 100644 --- a/spring-cloud-tencent-commons/pom.xml +++ b/spring-cloud-tencent-commons/pom.xml @@ -83,6 +83,18 @@ true + + org.springframework.boot + spring-boot-starter-web + true + + + + org.springframework.boot + spring-boot-starter-webflux + true + + org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java index 7e93fdb6a..26557aa3d 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java @@ -18,6 +18,7 @@ package com.tencent.cloud.common.util; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -29,6 +30,8 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; @@ -41,16 +44,50 @@ import org.springframework.web.server.ServerWebExchange; */ public class ExpressionLabelUtils { - private static final String LABEL_HEADER_PREFIX = "${http.header."; - private static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length(); - private static final String LABEL_QUERY_PREFIX = "${http.query."; - private static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length(); - private static final String LABEL_COOKIE_PREFIX = "${http.cookie."; - private static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length(); - private static final String LABEL_METHOD = "${http.method}"; - private static final String LABEL_URI = "${http.uri}"; - - private static final String LABEL_SUFFIX = "}"; + /** + * the expression prefix of header label. + */ + public static final String LABEL_HEADER_PREFIX = "${http.header."; + /** + * the length of expression header label prefix. + */ + public static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length(); + /** + * the expression prefix of query. + */ + public static final String LABEL_QUERY_PREFIX = "${http.query."; + /** + * the length of expression query label prefix. + */ + public static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length(); + /** + * the expression prefix of cookie. + */ + public static final String LABEL_COOKIE_PREFIX = "${http.cookie."; + /** + * the length of expression cookie label prefix. + */ + public static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length(); + /** + * the expression of method. + */ + public static final String LABEL_METHOD = "${http.method}"; + /** + * the expression of uri. + */ + public static final String LABEL_URI = "${http.uri}"; + /** + * the prefix of expression. + */ + public static final String LABEL_PREFIX = "${"; + /** + * the suffix of expression. + */ + public static final String LABEL_SUFFIX = "}"; + /** + * the escape prefix of label. + */ + public static final String LABEL_ESCAPE_PREFIX = "$$$$"; public static boolean isExpressionLabel(String labelKey) { if (StringUtils.isEmpty(labelKey)) { @@ -66,6 +103,14 @@ public class ExpressionLabelUtils { && StringUtils.endsWith(labelKey, LABEL_SUFFIX); } + public static String escape(String str) { + return StringUtils.replace(str, LABEL_PREFIX, LABEL_ESCAPE_PREFIX); + } + + public static String unescape(String str) { + return StringUtils.replace(str, LABEL_ESCAPE_PREFIX, LABEL_PREFIX); + } + public static Map resolve(HttpServletRequest request, Set labelKeys) { if (CollectionUtils.isEmpty(labelKeys)) { return Collections.emptyMap(); @@ -75,15 +120,24 @@ public class ExpressionLabelUtils { for (String labelKey : labelKeys) { if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) { - String headerKey = labelKey.substring(LABEL_HEADER_PREFIX_LEN, labelKey.length() - 1); + String headerKey = parseHeaderKey(labelKey); + if (StringUtils.isBlank(headerKey)) { + continue; + } labels.put(labelKey, request.getHeader(headerKey)); } else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) { - String queryKey = labelKey.substring(LABEL_QUERY_PREFIX_LEN, labelKey.length() - 1); + String queryKey = parseQueryKey(labelKey); + if (StringUtils.isBlank(queryKey)) { + continue; + } labels.put(labelKey, getQueryValue(request.getQueryString(), queryKey)); } else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) { - String cookieKey = labelKey.substring(LABEL_COOKIE_PREFIX_LEN, labelKey.length() - 1); + String cookieKey = parseCookieKey(labelKey); + if (StringUtils.isBlank(cookieKey)) { + continue; + } labels.put(labelKey, getCookieValue(request.getCookies(), cookieKey)); } else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { @@ -106,15 +160,24 @@ public class ExpressionLabelUtils { for (String labelKey : labelKeys) { if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) { - String headerKey = labelKey.substring(LABEL_HEADER_PREFIX_LEN, labelKey.length() - 1); + String headerKey = parseHeaderKey(labelKey); + if (StringUtils.isBlank(headerKey)) { + continue; + } labels.put(labelKey, getHeaderValue(exchange.getRequest(), headerKey)); } else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) { - String queryKey = labelKey.substring(LABEL_QUERY_PREFIX_LEN, labelKey.length() - 1); + String queryKey = parseQueryKey(labelKey); + if (StringUtils.isBlank(queryKey)) { + continue; + } labels.put(labelKey, getQueryValue(exchange.getRequest(), queryKey)); } else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) { - String cookieKey = labelKey.substring(LABEL_COOKIE_PREFIX_LEN, labelKey.length() - 1); + String cookieKey = parseCookieKey(labelKey); + if (StringUtils.isBlank(cookieKey)) { + continue; + } labels.put(labelKey, getCookieValue(exchange.getRequest(), cookieKey)); } else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { @@ -128,7 +191,52 @@ public class ExpressionLabelUtils { return labels; } - private static String getQueryValue(String queryString, String queryKey) { + public static Map resolve(HttpRequest request, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + Map labels = new HashMap<>(); + + for (String labelKey : labelKeys) { + if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) { + String headerKey = parseHeaderKey(labelKey); + if (StringUtils.isBlank(headerKey)) { + continue; + } + labels.put(labelKey, getHeaderValue(request, headerKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) { + String queryKey = parseQueryKey(labelKey); + if (StringUtils.isBlank(queryKey)) { + continue; + } + labels.put(labelKey, getQueryValue(request, queryKey)); + } + else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { + labels.put(labelKey, request.getMethodValue()); + } + else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) { + labels.put(labelKey, request.getURI().getPath()); + } + } + + return labels; + } + + public static String parseHeaderKey(String expression) { + return expression.substring(LABEL_HEADER_PREFIX_LEN, expression.length() - 1); + } + + public static String parseQueryKey(String expression) { + return expression.substring(LABEL_QUERY_PREFIX_LEN, expression.length() - 1); + } + + public static String parseCookieKey(String expression) { + return expression.substring(LABEL_COOKIE_PREFIX_LEN, expression.length() - 1); + } + + public static String getQueryValue(String queryString, String queryKey) { if (StringUtils.isBlank(queryString)) { return StringUtils.EMPTY; } @@ -145,7 +253,7 @@ public class ExpressionLabelUtils { return StringUtils.EMPTY; } - private static String getCookieValue(Cookie[] cookies, String key) { + public static String getCookieValue(Cookie[] cookies, String key) { if (cookies == null || cookies.length == 0) { return StringUtils.EMPTY; } @@ -157,7 +265,7 @@ public class ExpressionLabelUtils { return StringUtils.EMPTY; } - private static String getHeaderValue(ServerHttpRequest request, String key) { + public static String getHeaderValue(ServerHttpRequest request, String key) { String value = request.getHeaders().getFirst(key); if (value == null) { return StringUtils.EMPTY; @@ -165,7 +273,7 @@ public class ExpressionLabelUtils { return value; } - private static String getQueryValue(ServerHttpRequest request, String key) { + public static String getQueryValue(ServerHttpRequest request, String key) { MultiValueMap queries = request.getQueryParams(); if (CollectionUtils.isEmpty(queries)) { return StringUtils.EMPTY; @@ -177,11 +285,39 @@ public class ExpressionLabelUtils { return value; } - private static String getCookieValue(ServerHttpRequest request, String key) { + public static String getCookieValue(ServerHttpRequest request, String key) { HttpCookie cookie = request.getCookies().getFirst(key); if (cookie == null) { return StringUtils.EMPTY; } return cookie.getValue(); } + + public static String getHeaderValue(HttpRequest request, String key) { + HttpHeaders headers = request.getHeaders(); + return headers.getFirst(key); + } + + public static String getQueryValue(HttpRequest request, String key) { + String query = request.getURI().getQuery(); + return getQueryValue(query, key); + } + + public static String getFirstValue(Map> valueMaps, String key) { + if (CollectionUtils.isEmpty(valueMaps)) { + return StringUtils.EMPTY; + } + + Collection values = valueMaps.get(key); + + if (CollectionUtils.isEmpty(values)) { + return StringUtils.EMPTY; + } + + for (String value : values) { + return value; + } + + return StringUtils.EMPTY; + } } diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/ServiceRuleManager.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/ServiceRuleManager.java index a615c5c80..8b3fb16d4 100644 --- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/ServiceRuleManager.java +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/ServiceRuleManager.java @@ -75,7 +75,7 @@ public class ServiceRuleManager { return null; } - public List getServiceRouterRule(String namespace, String sourceService, String dstService) { + public List getServiceRouterRule(String namespace, String sourceService, String dstService) { Set routerKeys = new HashSet<>(); ServiceEventKey dstSvcEventKey = new ServiceEventKey(new ServiceKey(namespace, dstService), @@ -92,20 +92,23 @@ public class ServiceRuleManager { ResourcesResponse resourcesResponse = BaseFlow .syncGetResources(sdkContext.getExtensions(), true, svcKeysProvider, controlParam); - List rules = new ArrayList<>(); + List rules = new ArrayList<>(); + + //get source service outbound rules. ServiceRule sourceServiceRule = resourcesResponse.getServiceRule(srcSvcEventKey); if (sourceServiceRule != null) { Object rule = sourceServiceRule.getRule(); if (rule instanceof RoutingProto.Routing) { - rules.add((RoutingProto.Routing) rule); + rules.addAll(((RoutingProto.Routing) rule).getOutboundsList()); } } + //get peer service inbound rules. ServiceRule dstServiceRule = resourcesResponse.getServiceRule(dstSvcEventKey); if (dstServiceRule != null) { Object rule = dstServiceRule.getRule(); if (rule instanceof RoutingProto.Routing) { - rules.add((RoutingProto.Routing) rule); + rules.addAll(((RoutingProto.Routing) rule).getInboundsList()); } }