Merge pull request #183 from lepdou/h/expression_rule_label
support parse ratelimit rule expression labelspull/190/head
commit
cb103080fa
@ -1,5 +1,6 @@
|
||||
# Change Log
|
||||
---
|
||||
|
||||
- [fix:Router support request label.](https://github.com/Tencent/spring-cloud-tencent/pull/165)
|
||||
|
||||
- [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)
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
|
||||
*
|
||||
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.polaris.ratelimit;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.tencent.cloud.common.util.ExpressionLabelUtils;
|
||||
import com.tencent.cloud.polaris.context.ServiceRuleManager;
|
||||
import com.tencent.polaris.client.pb.ModelProto;
|
||||
import com.tencent.polaris.client.pb.RateLimitProto;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* resolve labels from rate limit rule.
|
||||
*
|
||||
*@author lepdou 2022-05-13
|
||||
*/
|
||||
public class RateLimitRuleLabelResolver {
|
||||
|
||||
private final ServiceRuleManager serviceRuleManager;
|
||||
|
||||
public RateLimitRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
|
||||
this.serviceRuleManager = serviceRuleManager;
|
||||
}
|
||||
|
||||
public Set<String> getExpressionLabelKeys(String namespace, String service) {
|
||||
RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service);
|
||||
if (rateLimitRule == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
List<RateLimitProto.Rule> rules = rateLimitRule.getRulesList();
|
||||
if (CollectionUtils.isEmpty(rules)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
Set<String> expressionLabels = new HashSet<>();
|
||||
for (RateLimitProto.Rule rule : rules) {
|
||||
Map<String, ModelProto.MatchString> labels = rule.getLabelsMap();
|
||||
if (CollectionUtils.isEmpty(labels)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
for (String key : labels.keySet()) {
|
||||
if (ExpressionLabelUtils.isExpressionLabel(key)) {
|
||||
expressionLabels.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return expressionLabels;
|
||||
}
|
||||
}
|
@ -0,0 +1,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<String, String> resolve(HttpServletRequest request, Set<String> labelKeys) {
|
||||
if (CollectionUtils.isEmpty(labelKeys)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, String> labels = new HashMap<>();
|
||||
|
||||
for (String labelKey : labelKeys) {
|
||||
if (StringUtils.startsWithIgnoreCase(labelKey, 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<String, String> resolve(ServerWebExchange exchange, Set<String> labelKeys) {
|
||||
if (CollectionUtils.isEmpty(labelKeys)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, String> 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<String, String> 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();
|
||||
}
|
||||
}
|
@ -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<RoutingProto.Routing> getServiceRouterRule(String namespace, String sourceService, String dstService) {
|
||||
Set<ServiceEventKey> 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<RoutingProto.Routing> 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;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue