From 83338d7b1d8e648a38f6303b0746a50fc2c27295 Mon Sep 17 00:00:00 2001 From: lepdou Date: Thu, 19 May 2022 20:39:06 +0800 Subject: [PATCH] support parse ratelimit rule expression labels --- CHANGELOG.md | 2 +- changes/changes-1.4.4.md | 4 + .../ratelimit/RateLimitRuleLabelResolver.java | 72 +++++++ .../config/RateLimitConfiguration.java | 17 +- .../filter/QuotaCheckReactiveFilter.java | 73 ++++--- .../filter/QuotaCheckServletFilter.java | 84 +++++--- .../common/util/ExpressionLabelUtils.java | 187 ++++++++++++++++++ .../PolarisContextAutoConfiguration.java | 4 + .../polaris/context/ServiceRuleManager.java | 114 +++++++++++ 9 files changed, 497 insertions(+), 60 deletions(-) create mode 100644 changes/changes-1.4.4.md create mode 100644 spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java create mode 100644 spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/ServiceRuleManager.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa0ff74..8954bd05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ # Change Log --- -- [fix:fix wrong isAliveFlag config when creating new PolarisServer.](https://github.com/Tencent/spring-cloud-tencent/pull/179) +- [Feature: Support parse ratelimit rule expression labels.](https://github.com/Tencent/spring-cloud-tencent/pull/182) diff --git a/changes/changes-1.4.4.md b/changes/changes-1.4.4.md new file mode 100644 index 00000000..1aa0ff74 --- /dev/null +++ b/changes/changes-1.4.4.md @@ -0,0 +1,4 @@ +# Change Log +--- + +- [fix:fix wrong isAliveFlag config when creating new PolarisServer.](https://github.com/Tencent/spring-cloud-tencent/pull/179) diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java new file mode 100644 index 00000000..4c29cb3a --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java @@ -0,0 +1,72 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.ratelimit; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.polaris.context.ServiceRuleManager; +import com.tencent.polaris.client.pb.ModelProto; +import com.tencent.polaris.client.pb.RateLimitProto; + +import org.springframework.util.CollectionUtils; + +/** + * resolve labels from rate limit rule. + * + *@author lepdou 2022-05-13 + */ +public class RateLimitRuleLabelResolver { + + private final ServiceRuleManager serviceRuleManager; + + public RateLimitRuleLabelResolver(ServiceRuleManager serviceRuleManager) { + this.serviceRuleManager = serviceRuleManager; + } + + public Set getExpressionLabelKeys(String namespace, String service) { + RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service); + if (rateLimitRule == null) { + return Collections.emptySet(); + } + + List rules = rateLimitRule.getRulesList(); + if (CollectionUtils.isEmpty(rules)) { + return Collections.emptySet(); + } + + Set expressionLabels = new HashSet<>(); + for (RateLimitProto.Rule rule : rules) { + Map labels = rule.getLabelsMap(); + if (CollectionUtils.isEmpty(labels)) { + return Collections.emptySet(); + } + for (String key : labels.keySet()) { + if (ExpressionLabelUtils.isExpressionLabel(key)) { + expressionLabels.add(key); + } + } + } + return expressionLabels; + } +} diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/RateLimitConfiguration.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/RateLimitConfiguration.java index cf4d33a9..bf912674 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/RateLimitConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/RateLimitConfiguration.java @@ -19,6 +19,8 @@ package com.tencent.cloud.polaris.ratelimit.config; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; +import com.tencent.cloud.polaris.context.ServiceRuleManager; +import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter; @@ -62,6 +64,11 @@ public class RateLimitConfiguration { return LimitAPIFactory.createLimitAPIByContext(polarisContext); } + @Bean + public RateLimitRuleLabelResolver rateLimitRuleLabelService(ServiceRuleManager serviceRuleManager) { + return new RateLimitRuleLabelResolver(serviceRuleManager); + } + /** * Create when web application type is SERVLET. */ @@ -73,9 +80,10 @@ public class RateLimitConfiguration { @ConditionalOnMissingBean public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI, @Nullable PolarisRateLimiterLabelServletResolver labelResolver, - PolarisRateLimitProperties polarisRateLimitProperties) { + PolarisRateLimitProperties polarisRateLimitProperties, + RateLimitRuleLabelResolver rateLimitRuleLabelResolver) { return new QuotaCheckServletFilter(limitAPI, labelResolver, - polarisRateLimitProperties); + polarisRateLimitProperties, rateLimitRuleLabelResolver); } @Bean @@ -101,9 +109,10 @@ public class RateLimitConfiguration { @Bean public QuotaCheckReactiveFilter quotaCheckReactiveFilter(LimitAPI limitAPI, @Nullable PolarisRateLimiterLabelReactiveResolver labelResolver, - PolarisRateLimitProperties polarisRateLimitProperties) { + PolarisRateLimitProperties polarisRateLimitProperties, + RateLimitRuleLabelResolver rateLimitRuleLabelResolver) { return new QuotaCheckReactiveFilter(limitAPI, labelResolver, - polarisRateLimitProperties); + polarisRateLimitProperties, rateLimitRuleLabelResolver); } } diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java index 9a0db9a7..d9b8d338 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java @@ -21,10 +21,14 @@ package com.tencent.cloud.polaris.ratelimit.filter; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.Set; import javax.annotation.PostConstruct; +import com.google.common.collect.Maps; import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver; @@ -42,7 +46,6 @@ import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.util.CollectionUtils; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; @@ -52,7 +55,7 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB /** * Reactive filter to check quota. * - * @author Haotian Zhang + * @author Haotian Zhang, lepdou */ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { @@ -65,14 +68,18 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { private final PolarisRateLimitProperties polarisRateLimitProperties; + private final RateLimitRuleLabelResolver rateLimitRuleLabelResolver; + private String rejectTips; public QuotaCheckReactiveFilter(LimitAPI limitAPI, PolarisRateLimiterLabelReactiveResolver labelResolver, - PolarisRateLimitProperties polarisRateLimitProperties) { + PolarisRateLimitProperties polarisRateLimitProperties, + RateLimitRuleLabelResolver rateLimitRuleLabelResolver) { this.limitAPI = limitAPI; this.labelResolver = labelResolver; this.polarisRateLimitProperties = polarisRateLimitProperties; + this.rateLimitRuleLabelResolver = rateLimitRuleLabelResolver; } @PostConstruct @@ -90,27 +97,7 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localService = MetadataContext.LOCAL_SERVICE; - Map labels = new HashMap<>(); - - // add build in labels - String path = exchange.getRequest().getURI().getPath(); - if (StringUtils.isNotBlank(path)) { - labels.put(LABEL_METHOD, path); - } - - // add custom labels - if (labelResolver != null) { - try { - Map customLabels = labelResolver.resolve(exchange); - if (!CollectionUtils.isEmpty(customLabels)) { - labels.putAll(customLabels); - } - } - catch (Throwable e) { - LOG.error("resolve custom label failed. resolver = {}", - labelResolver.getClass().getName(), e); - } - } + Map labels = getRequestLabels(exchange, localNamespace, localService); try { QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, @@ -134,4 +121,42 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { return chain.filter(exchange); } + private Map getRequestLabels(ServerWebExchange exchange, String localNamespace, String localService) { + Map labels = new HashMap<>(); + + // add build in labels + String path = exchange.getRequest().getURI().getPath(); + if (StringUtils.isNotBlank(path)) { + labels.put(LABEL_METHOD, path); + } + + // add rule expression labels + Map expressionLabels = getRuleExpressionLabels(exchange, localNamespace, localService); + labels.putAll(expressionLabels); + + // add custom labels + Map customResolvedLabels = getCustomResolvedLabels(exchange); + labels.putAll(customResolvedLabels); + + return labels; + } + + private Map getCustomResolvedLabels(ServerWebExchange exchange) { + if (labelResolver != null) { + try { + return labelResolver.resolve(exchange); + } + catch (Throwable e) { + LOG.error("resolve custom label failed. resolver = {}", + labelResolver.getClass().getName(), e); + } + } + return Maps.newHashMap(); + } + + private Map getRuleExpressionLabels(ServerWebExchange exchange, String namespace, String service) { + Set expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service); + return ExpressionLabelUtils.resolve(exchange, expressionLabels); + } + } diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java index ae160109..54a9e545 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java @@ -19,8 +19,10 @@ package com.tencent.cloud.polaris.ratelimit.filter; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import javax.annotation.PostConstruct; import javax.servlet.FilterChain; @@ -29,6 +31,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; @@ -42,7 +46,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; -import org.springframework.util.CollectionUtils; import org.springframework.web.filter.OncePerRequestFilter; import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LABEL_METHOD; @@ -50,13 +53,12 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB /** * Servlet filter to check quota. * - * @author Haotian Zhang + * @author Haotian Zhang, lepdou */ @Order(RateLimitConstant.FILTER_ORDER) public class QuotaCheckServletFilter extends OncePerRequestFilter { - private static final Logger LOG = LoggerFactory - .getLogger(QuotaCheckServletFilter.class); + private static final Logger LOG = LoggerFactory.getLogger(QuotaCheckServletFilter.class); private final LimitAPI limitAPI; @@ -64,14 +66,18 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter { private final PolarisRateLimitProperties polarisRateLimitProperties; + private final RateLimitRuleLabelResolver rateLimitRuleLabelResolver; + private String rejectTips; public QuotaCheckServletFilter(LimitAPI limitAPI, PolarisRateLimiterLabelServletResolver labelResolver, - PolarisRateLimitProperties polarisRateLimitProperties) { + PolarisRateLimitProperties polarisRateLimitProperties, + RateLimitRuleLabelResolver rateLimitRuleLabelResolver) { this.limitAPI = limitAPI; this.labelResolver = labelResolver; this.polarisRateLimitProperties = polarisRateLimitProperties; + this.rateLimitRuleLabelResolver = rateLimitRuleLabelResolver; } @PostConstruct @@ -80,52 +86,68 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter { } @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, FilterChain filterChain) + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localService = MetadataContext.LOCAL_SERVICE; + Map labels = getRequestLabels(request, localNamespace, localService); + + try { + QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, + localNamespace, localService, 1, labels, null); + + if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { + response.setStatus(polarisRateLimitProperties.getRejectHttpCode()); + response.getWriter().write(rejectTips); + return; + } + + filterChain.doFilter(request, response); + } + catch (Throwable t) { + // An exception occurs in the rate limiting API call, + // which should not affect the call of the business process. + LOG.error("fail to invoke getQuota, service is " + localService, t); + filterChain.doFilter(request, response); + } + } + + private Map getRequestLabels(HttpServletRequest request, String localNamespace, String localService) { Map labels = new HashMap<>(); // add build in labels String path = request.getRequestURI(); - if (StringUtils.isNotBlank(path)) { labels.put(LABEL_METHOD, path); } - // add custom labels + // add rule expression labels + Map expressionLabels = getRuleExpressionLabels(request, localNamespace, localService); + labels.putAll(expressionLabels); + + // add custom resolved labels + Map customLabels = getCustomResolvedLabels(request); + labels.putAll(customLabels); + + return labels; + } + + private Map getCustomResolvedLabels(HttpServletRequest request) { if (labelResolver != null) { try { - Map customLabels = labelResolver.resolve(request); - if (!CollectionUtils.isEmpty(customLabels)) { - labels.putAll(customLabels); - } + return labelResolver.resolve(request); } catch (Throwable e) { LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e); } } - - try { - QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, - localNamespace, localService, 1, labels, null); - if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { - response.setStatus(polarisRateLimitProperties.getRejectHttpCode()); - response.getWriter().write(rejectTips); - } - else { - filterChain.doFilter(request, response); - } - } - catch (Throwable t) { - // An exception occurs in the rate limiting API call, - // which should not affect the call of the business process. - LOG.error("fail to invoke getQuota, service is " + localService, t); - filterChain.doFilter(request, response); - } + return Collections.emptyMap(); } + private Map getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) { + Set expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service); + return ExpressionLabelUtils.resolve(request, expressionLabels); + } } 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 new file mode 100644 index 00000000..7e93fdb6 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java @@ -0,0 +1,187 @@ +/* + * 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.common.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; + +import org.springframework.http.HttpCookie; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.CollectionUtils; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; + +/** + * the utils for parse label expression. + * + *@author lepdou 2022-05-13 + */ +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 = "}"; + + public static boolean isExpressionLabel(String labelKey) { + if (StringUtils.isEmpty(labelKey)) { + return false; + } + if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey) || + StringUtils.startsWithIgnoreCase(LABEL_URI, labelKey)) { + return true; + } + return (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX) || + StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX) || + StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) + && StringUtils.endsWith(labelKey, LABEL_SUFFIX); + } + + public static Map resolve(HttpServletRequest 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 = labelKey.substring(LABEL_HEADER_PREFIX_LEN, labelKey.length() - 1); + 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); + 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); + labels.put(labelKey, getCookieValue(request.getCookies(), cookieKey)); + } + else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { + labels.put(labelKey, request.getMethod()); + } + else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) { + labels.put(labelKey, request.getRequestURI()); + } + } + + return labels; + } + + public static Map resolve(ServerWebExchange exchange, 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 = labelKey.substring(LABEL_HEADER_PREFIX_LEN, labelKey.length() - 1); + 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); + 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); + labels.put(labelKey, getCookieValue(exchange.getRequest(), cookieKey)); + } + else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { + labels.put(labelKey, exchange.getRequest().getMethodValue()); + } + else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) { + labels.put(labelKey, exchange.getRequest().getURI().getPath()); + } + } + + return labels; + } + + private static String getQueryValue(String queryString, String queryKey) { + if (StringUtils.isBlank(queryString)) { + return StringUtils.EMPTY; + } + String[] queries = StringUtils.split(queryString, "&"); + if (queries == null || queries.length == 0) { + return StringUtils.EMPTY; + } + for (String query : queries) { + String[] queryKV = StringUtils.split(query, "="); + if (queryKV != null && queryKV.length == 2 && StringUtils.equals(queryKV[0], queryKey)) { + return queryKV[1]; + } + } + return StringUtils.EMPTY; + } + + private static String getCookieValue(Cookie[] cookies, String key) { + if (cookies == null || cookies.length == 0) { + return StringUtils.EMPTY; + } + for (Cookie cookie : cookies) { + if (StringUtils.equals(cookie.getName(), key)) { + return cookie.getValue(); + } + } + return StringUtils.EMPTY; + } + + private static String getHeaderValue(ServerHttpRequest request, String key) { + String value = request.getHeaders().getFirst(key); + if (value == null) { + return StringUtils.EMPTY; + } + return value; + } + + private static String getQueryValue(ServerHttpRequest request, String key) { + MultiValueMap queries = request.getQueryParams(); + if (CollectionUtils.isEmpty(queries)) { + return StringUtils.EMPTY; + } + String value = queries.getFirst(key); + if (value == null) { + return StringUtils.EMPTY; + } + return value; + } + + private static String getCookieValue(ServerHttpRequest request, String key) { + HttpCookie cookie = request.getCookies().getFirst(key); + if (cookie == null) { + return StringUtils.EMPTY; + } + return cookie.getValue(); + } +} diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextAutoConfiguration.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextAutoConfiguration.java index 11e08128..db8424a3 100644 --- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextAutoConfiguration.java +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextAutoConfiguration.java @@ -47,4 +47,8 @@ public class PolarisContextAutoConfiguration { return new ModifyAddress(); } + @Bean + public ServiceRuleManager serviceRuleManager(SDKContext sdkContext) { + return new ServiceRuleManager(sdkContext); + } } 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 new file mode 100644 index 00000000..a615c5c8 --- /dev/null +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/ServiceRuleManager.java @@ -0,0 +1,114 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.context; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.tencent.polaris.api.pojo.DefaultServiceEventKeysProvider; +import com.tencent.polaris.api.pojo.ServiceEventKey; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.api.pojo.ServiceRule; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.client.flow.BaseFlow; +import com.tencent.polaris.client.flow.DefaultFlowControlParam; +import com.tencent.polaris.client.flow.FlowControlParam; +import com.tencent.polaris.client.flow.ResourcesResponse; +import com.tencent.polaris.client.pb.RateLimitProto; +import com.tencent.polaris.client.pb.RoutingProto; + +/** + * the manager of service governance rules. for example: rate limit rule, router rules. + * + *@author lepdou 2022-05-13 + */ +public class ServiceRuleManager { + + private final SDKContext sdkContext; + + private final FlowControlParam controlParam; + + public ServiceRuleManager(SDKContext sdkContext) { + this.sdkContext = sdkContext; + controlParam = new DefaultFlowControlParam(); + controlParam.setTimeoutMs(sdkContext.getConfig().getGlobal().getAPI().getTimeout()); + controlParam.setMaxRetry(sdkContext.getConfig().getGlobal().getAPI().getMaxRetryTimes()); + controlParam.setRetryIntervalMs(sdkContext.getConfig().getGlobal().getAPI().getRetryInterval()); + } + + public RateLimitProto.RateLimit getServiceRateLimitRule(String namespace, String service) { + ServiceEventKey serviceEventKey = new ServiceEventKey(new ServiceKey(namespace, service), + ServiceEventKey.EventType.RATE_LIMITING); + + DefaultServiceEventKeysProvider svcKeysProvider = new DefaultServiceEventKeysProvider(); + svcKeysProvider.setSvcEventKey(serviceEventKey); + + ResourcesResponse resourcesResponse = BaseFlow + .syncGetResources(sdkContext.getExtensions(), true, svcKeysProvider, controlParam); + + ServiceRule serviceRule = resourcesResponse.getServiceRule(serviceEventKey); + if (serviceRule != null) { + Object rule = serviceRule.getRule(); + if (rule instanceof RateLimitProto.RateLimit) { + return (RateLimitProto.RateLimit) rule; + } + } + + return null; + } + + public List getServiceRouterRule(String namespace, String sourceService, String dstService) { + Set routerKeys = new HashSet<>(); + + ServiceEventKey dstSvcEventKey = new ServiceEventKey(new ServiceKey(namespace, dstService), + ServiceEventKey.EventType.ROUTING); + routerKeys.add(dstSvcEventKey); + + ServiceEventKey srcSvcEventKey = new ServiceEventKey(new ServiceKey(namespace, sourceService), + ServiceEventKey.EventType.ROUTING); + + DefaultServiceEventKeysProvider svcKeysProvider = new DefaultServiceEventKeysProvider(); + svcKeysProvider.setSvcEventKeys(routerKeys); + + + ResourcesResponse resourcesResponse = BaseFlow + .syncGetResources(sdkContext.getExtensions(), true, svcKeysProvider, controlParam); + + List rules = new ArrayList<>(); + ServiceRule sourceServiceRule = resourcesResponse.getServiceRule(srcSvcEventKey); + if (sourceServiceRule != null) { + Object rule = sourceServiceRule.getRule(); + if (rule instanceof RoutingProto.Routing) { + rules.add((RoutingProto.Routing) rule); + } + } + + ServiceRule dstServiceRule = resourcesResponse.getServiceRule(dstSvcEventKey); + if (dstServiceRule != null) { + Object rule = dstServiceRule.getRule(); + if (rule instanceof RoutingProto.Routing) { + rules.add((RoutingProto.Routing) rule); + } + } + + return rules; + } +}