Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -22,6 +22,7 @@ | |||||
<module>sentinel-zuul-adapter</module> | <module>sentinel-zuul-adapter</module> | ||||
<module>sentinel-reactor-adapter</module> | <module>sentinel-reactor-adapter</module> | ||||
<module>sentinel-spring-webflux-adapter</module> | <module>sentinel-spring-webflux-adapter</module> | ||||
<module>sentinel-api-gateway-adapter-common</module> | |||||
</modules> | </modules> | ||||
<dependencyManagement> | <dependencyManagement> | ||||
@@ -46,6 +47,11 @@ | |||||
<artifactId>sentinel-reactor-adapter</artifactId> | <artifactId>sentinel-reactor-adapter</artifactId> | ||||
<version>${project.version}</version> | <version>${project.version}</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-api-gateway-adapter-common</artifactId> | |||||
<version>${project.version}</version> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>junit</groupId> | <groupId>junit</groupId> | ||||
@@ -0,0 +1,8 @@ | |||||
# Sentinel API Gateway Adapter Common | |||||
The `sentinel-api-gateway-adapter-common` module provides common abstraction for | |||||
API gateway flow control: | |||||
- `GatewayFlowRule`: flow control rule specific for route or API defined in API gateway. | |||||
This can be automatically converted to `FlowRule` or `ParamFlowRule`. | |||||
- `ApiDefinition`: gateway API definition with a group of predicates |
@@ -0,0 +1,36 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||||
<parent> | |||||
<artifactId>sentinel-adapter</artifactId> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<version>1.6.0-SNAPSHOT</version> | |||||
</parent> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<artifactId>sentinel-api-gateway-adapter-common</artifactId> | |||||
<packaging>jar</packaging> | |||||
<properties> | |||||
<java.source.version>1.7</java.source.version> | |||||
<java.target.version>1.7</java.target.version> | |||||
</properties> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-core</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-parameter-flow-control</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
</project> |
@@ -0,0 +1,45 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public final class SentinelGatewayConstants { | |||||
public static final int APP_TYPE_GATEWAY = 1; | |||||
public static final int RESOURCE_MODE_ROUTE_ID = 0; | |||||
public static final int RESOURCE_MODE_CUSTOM_API_NAME = 1; | |||||
public static final int PARAM_PARSE_STRATEGY_CLIENT_IP = 0; | |||||
public static final int PARAM_PARSE_STRATEGY_HOST = 1; | |||||
public static final int PARAM_PARSE_STRATEGY_HEADER = 2; | |||||
public static final int PARAM_PARSE_STRATEGY_URL_PARAM = 3; | |||||
public static final int PARAM_MATCH_STRATEGY_EXACT = 0; | |||||
public static final int PARAM_MATCH_STRATEGY_PREFIX = 1; | |||||
public static final int PARAM_MATCH_STRATEGY_REGEX = 2; | |||||
public static final String GATEWAY_CONTEXT_DEFAULT = "sentinel_gateway_context_default"; | |||||
public static final String GATEWAY_CONTEXT_PREFIX = "sentinel_gateway_context$$"; | |||||
public static final String GATEWAY_CONTEXT_ROUTE_PREFIX = "sentinel_gateway_context$$route$$"; | |||||
public static final String GATEWAY_NOT_MATCH_PARAM = "$$not_match"; | |||||
private SentinelGatewayConstants() {} | |||||
} |
@@ -0,0 +1,79 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.api; | |||||
import java.util.Objects; | |||||
import java.util.Set; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public class ApiDefinition { | |||||
private String apiName; | |||||
private Set<ApiPredicateItem> predicateItems; | |||||
public ApiDefinition() {} | |||||
public ApiDefinition(String apiName) { | |||||
this.apiName = apiName; | |||||
} | |||||
public String getApiName() { | |||||
return apiName; | |||||
} | |||||
public ApiDefinition setApiName(String apiName) { | |||||
this.apiName = apiName; | |||||
return this; | |||||
} | |||||
public Set<ApiPredicateItem> getPredicateItems() { | |||||
return predicateItems; | |||||
} | |||||
public ApiDefinition setPredicateItems(Set<ApiPredicateItem> predicateItems) { | |||||
this.predicateItems = predicateItems; | |||||
return this; | |||||
} | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) { return true; } | |||||
if (o == null || getClass() != o.getClass()) { return false; } | |||||
ApiDefinition that = (ApiDefinition)o; | |||||
if (!Objects.equals(apiName, that.apiName)) { return false; } | |||||
return Objects.equals(predicateItems, that.predicateItems); | |||||
} | |||||
@Override | |||||
public int hashCode() { | |||||
int result = apiName != null ? apiName.hashCode() : 0; | |||||
result = 31 * result + (predicateItems != null ? predicateItems.hashCode() : 0); | |||||
return result; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "ApiDefinition{" + | |||||
"apiName='" + apiName + '\'' + | |||||
", predicateItems=" + predicateItems + | |||||
'}'; | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.api; | |||||
import java.util.Set; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public interface ApiDefinitionChangeObserver { | |||||
/** | |||||
* Notify the observer about the new gateway API definitions. | |||||
* | |||||
* @param apiDefinitions new set of gateway API definition | |||||
*/ | |||||
void onChange(Set<ApiDefinition> apiDefinitions); | |||||
} |
@@ -0,0 +1,72 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.api; | |||||
import java.util.Objects; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public class ApiPathPredicateItem implements ApiPredicateItem { | |||||
private String pattern; | |||||
private int matchStrategy; | |||||
public ApiPathPredicateItem setPattern(String pattern) { | |||||
this.pattern = pattern; | |||||
return this; | |||||
} | |||||
public ApiPathPredicateItem setMatchStrategy(int matchStrategy) { | |||||
this.matchStrategy = matchStrategy; | |||||
return this; | |||||
} | |||||
public String getPattern() { | |||||
return pattern; | |||||
} | |||||
public int getMatchStrategy() { | |||||
return matchStrategy; | |||||
} | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) { return true; } | |||||
if (o == null || getClass() != o.getClass()) { return false; } | |||||
ApiPathPredicateItem that = (ApiPathPredicateItem)o; | |||||
if (matchStrategy != that.matchStrategy) { return false; } | |||||
return Objects.equals(pattern, that.pattern); | |||||
} | |||||
@Override | |||||
public int hashCode() { | |||||
int result = pattern != null ? pattern.hashCode() : 0; | |||||
result = 31 * result + matchStrategy; | |||||
return result; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "ApiPathPredicateItem{" + | |||||
"pattern='" + pattern + '\'' + | |||||
", matchStrategy=" + matchStrategy + | |||||
'}'; | |||||
} | |||||
} |
@@ -0,0 +1,46 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.api; | |||||
import java.util.HashSet; | |||||
import java.util.Set; | |||||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public class ApiPredicateGroupItem implements ApiPredicateItem { | |||||
private final Set<ApiPredicateItem> items = new HashSet<>(); | |||||
public ApiPredicateGroupItem addItem(ApiPredicateItem item) { | |||||
AssertUtil.notNull(item, "item cannot be null"); | |||||
items.add(item); | |||||
return this; | |||||
} | |||||
public Set<ApiPredicateItem> getItems() { | |||||
return items; | |||||
} | |||||
/*@Override | |||||
public ApiPredicateItem and(ApiPredicateItem item) { | |||||
AssertUtil.notNull(item, "item cannot be null"); | |||||
return this.addItem(item); | |||||
}*/ | |||||
} |
@@ -0,0 +1,37 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.api; | |||||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public interface ApiPredicateItem { | |||||
/** | |||||
* Combine two {@link ApiPredicateItem}. | |||||
* | |||||
* @param item another predicate item | |||||
* @return combined predicate group item | |||||
*/ | |||||
/*default ApiPredicateItem and(ApiPredicateItem item) { | |||||
AssertUtil.notNull(item, "item cannot be null"); | |||||
return new ApiPredicateGroupItem() | |||||
.addItem(this).addItem(item); | |||||
}*/ | |||||
} |
@@ -0,0 +1,162 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.api; | |||||
import java.util.HashMap; | |||||
import java.util.HashSet; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.Set; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; | |||||
import com.alibaba.csp.sentinel.property.PropertyListener; | |||||
import com.alibaba.csp.sentinel.property.SentinelProperty; | |||||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
/** | |||||
* Manager for gateway API definitions. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public final class GatewayApiDefinitionManager { | |||||
private static final Map<String, ApiDefinition> API_MAP = new ConcurrentHashMap<>(); | |||||
private static final ApiDefinitionPropertyListener LISTENER = new ApiDefinitionPropertyListener(); | |||||
private static SentinelProperty<Set<ApiDefinition>> currentProperty = new DynamicSentinelProperty<>(); | |||||
/** | |||||
* The map keeps all found ApiDefinitionChangeObserver (class name as key). | |||||
*/ | |||||
private static final Map<String, ApiDefinitionChangeObserver> API_CHANGE_OBSERVERS = new ConcurrentHashMap<>(); | |||||
static { | |||||
try { | |||||
currentProperty.addListener(LISTENER); | |||||
initializeApiChangeObserverSpi(); | |||||
} catch (Throwable ex) { | |||||
RecordLog.warn("[GatewayApiDefinitionManager] Failed to initialize", ex); | |||||
ex.printStackTrace(); | |||||
} | |||||
} | |||||
private static void initializeApiChangeObserverSpi() { | |||||
List<ApiDefinitionChangeObserver> listeners = SpiLoader.loadInstanceList(ApiDefinitionChangeObserver.class); | |||||
for (ApiDefinitionChangeObserver e : listeners) { | |||||
API_CHANGE_OBSERVERS.put(e.getClass().getCanonicalName(), e); | |||||
RecordLog.info("[GatewayApiDefinitionManager] ApiDefinitionChangeObserver added: {0}", | |||||
e.getClass().getCanonicalName()); | |||||
} | |||||
} | |||||
public static void register2Property(SentinelProperty<Set<ApiDefinition>> property) { | |||||
AssertUtil.notNull(property, "property cannot be null"); | |||||
synchronized (LISTENER) { | |||||
RecordLog.info("[GatewayApiDefinitionManager] Registering new property to gateway API definition manager"); | |||||
currentProperty.removeListener(LISTENER); | |||||
property.addListener(LISTENER); | |||||
currentProperty = property; | |||||
} | |||||
} | |||||
/** | |||||
* Load given gateway API definitions and apply to downstream observers. | |||||
* | |||||
* @param apiDefinitions set of gateway API definitions | |||||
* @return true if updated, or else false | |||||
*/ | |||||
public static boolean loadApiDefinitions(Set<ApiDefinition> apiDefinitions) { | |||||
return currentProperty.updateValue(apiDefinitions); | |||||
} | |||||
public static ApiDefinition getApiDefinition(final String apiName) { | |||||
if (apiName == null) { | |||||
return null; | |||||
} | |||||
return API_MAP.get(apiName); | |||||
} | |||||
public static Set<ApiDefinition> getApiDefinitions() { | |||||
return new HashSet<>(API_MAP.values()); | |||||
} | |||||
private static final class ApiDefinitionPropertyListener implements PropertyListener<Set<ApiDefinition>> { | |||||
@Override | |||||
public void configUpdate(Set<ApiDefinition> set) { | |||||
applyApiUpdateInternal(set); | |||||
RecordLog.info("[GatewayApiDefinitionManager] Api definition updated: " + API_MAP); | |||||
} | |||||
@Override | |||||
public void configLoad(Set<ApiDefinition> set) { | |||||
applyApiUpdateInternal(set); | |||||
RecordLog.info("[GatewayApiDefinitionManager] Api definition loaded: " + API_MAP); | |||||
} | |||||
private static synchronized void applyApiUpdateInternal(Set<ApiDefinition> set) { | |||||
if (set == null || set.isEmpty()) { | |||||
API_MAP.clear(); | |||||
notifyDownstreamListeners(new HashSet<ApiDefinition>()); | |||||
return; | |||||
} | |||||
Map<String, ApiDefinition> map = new HashMap<>(set.size()); | |||||
Set<ApiDefinition> validSet = new HashSet<>(); | |||||
for (ApiDefinition definition : set) { | |||||
if (isValidApi(definition)) { | |||||
map.put(definition.getApiName(), definition); | |||||
validSet.add(definition); | |||||
} | |||||
} | |||||
API_MAP.clear(); | |||||
API_MAP.putAll(map); | |||||
// propagate to downstream. | |||||
notifyDownstreamListeners(validSet); | |||||
} | |||||
} | |||||
private static void notifyDownstreamListeners(/*@Valid*/ final Set<ApiDefinition> definitions) { | |||||
try { | |||||
for (Map.Entry<?, ApiDefinitionChangeObserver> entry : API_CHANGE_OBSERVERS.entrySet()) { | |||||
entry.getValue().onChange(definitions); | |||||
} | |||||
} catch (Exception ex) { | |||||
RecordLog.warn("[GatewayApiDefinitionManager] WARN: failed to notify downstream api listeners", ex); | |||||
} | |||||
} | |||||
public static boolean isValidApi(ApiDefinition apiDefinition) { | |||||
return apiDefinition != null && StringUtil.isNotBlank(apiDefinition.getApiName()) | |||||
&& apiDefinition.getPredicateItems() != null; | |||||
} | |||||
static void addApiChangeListener(ApiDefinitionChangeObserver listener) { | |||||
AssertUtil.notNull(listener, "listener cannot be null"); | |||||
API_CHANGE_OBSERVERS.put(listener.getClass().getCanonicalName(), listener); | |||||
} | |||||
static void removeApiChangeListener(Class<?> clazz) { | |||||
AssertUtil.notNull(clazz, "class cannot be null"); | |||||
API_CHANGE_OBSERVERS.remove(clazz.getCanonicalName()); | |||||
} | |||||
} |
@@ -0,0 +1,74 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.api.matcher; | |||||
import java.util.HashSet; | |||||
import java.util.Set; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||||
import com.alibaba.csp.sentinel.util.function.Predicate; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public abstract class AbstractApiMatcher<T> implements Predicate<T> { | |||||
protected final String apiName; | |||||
protected final ApiDefinition apiDefinition; | |||||
/** | |||||
* We use {@link com.alibaba.csp.sentinel.util.function.Predicate} here as the min JDK version is 1.7. | |||||
*/ | |||||
protected final Set<Predicate<T>> matchers = new HashSet<>(); | |||||
public AbstractApiMatcher(ApiDefinition apiDefinition) { | |||||
AssertUtil.notNull(apiDefinition, "apiDefinition cannot be null"); | |||||
AssertUtil.assertNotBlank(apiDefinition.getApiName(), "apiName cannot be empty"); | |||||
this.apiName = apiDefinition.getApiName(); | |||||
this.apiDefinition = apiDefinition; | |||||
try { | |||||
initializeMatchers(); | |||||
} catch (Exception ex) { | |||||
RecordLog.warn("[GatewayApiMatcher] Failed to initialize internal matchers", ex); | |||||
} | |||||
} | |||||
/** | |||||
* Initialize the matchers. | |||||
*/ | |||||
protected abstract void initializeMatchers(); | |||||
@Override | |||||
public boolean test(T t) { | |||||
for (Predicate<T> matcher : matchers) { | |||||
if (matcher.test(t)) { | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
public String getApiName() { | |||||
return apiName; | |||||
} | |||||
public ApiDefinition getApiDefinition() { | |||||
return apiDefinition; | |||||
} | |||||
} |
@@ -0,0 +1,145 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.param; | |||||
import java.util.HashSet; | |||||
import java.util.Set; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; | |||||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.csp.sentinel.util.function.Predicate; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public class GatewayParamParser<T> { | |||||
private final RequestItemParser<T> requestItemParser; | |||||
public GatewayParamParser(RequestItemParser<T> requestItemParser) { | |||||
AssertUtil.notNull(requestItemParser, "requestItemParser cannot be null"); | |||||
this.requestItemParser = requestItemParser; | |||||
} | |||||
/** | |||||
* Parse parameters for given resource from the request entity on condition of the rule predicate. | |||||
* | |||||
* @param resource valid resource name | |||||
* @param request valid request | |||||
* @param rulePredicate rule predicate indicating the rules to refer | |||||
* @return the parameter array | |||||
*/ | |||||
public Object[] parseParameterFor(String resource, T request, Predicate<GatewayFlowRule> rulePredicate) { | |||||
if (StringUtil.isEmpty(resource) || request == null || rulePredicate == null) { | |||||
return new Object[0]; | |||||
} | |||||
Set<GatewayFlowRule> gatewayRules = new HashSet<>(); | |||||
Set<Boolean> predSet = new HashSet<>(); | |||||
for (GatewayFlowRule rule : GatewayRuleManager.getRulesForResource(resource)) { | |||||
if (rule.getParamItem() != null) { | |||||
gatewayRules.add(rule); | |||||
predSet.add(rulePredicate.test(rule)); | |||||
} | |||||
} | |||||
if (gatewayRules.isEmpty()) { | |||||
return new Object[0]; | |||||
} | |||||
if (predSet.size() != 1 || predSet.contains(false)) { | |||||
return new Object[0]; | |||||
} | |||||
Object[] arr = new Object[gatewayRules.size()]; | |||||
for (GatewayFlowRule rule : gatewayRules) { | |||||
GatewayParamFlowItem paramItem = rule.getParamItem(); | |||||
int idx = paramItem.getIndex(); | |||||
String param = parseInternal(paramItem, request); | |||||
arr[idx] = param; | |||||
} | |||||
return arr; | |||||
} | |||||
private String parseInternal(GatewayParamFlowItem item, T request) { | |||||
switch (item.getParseStrategy()) { | |||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP: | |||||
return parseClientIp(item, request); | |||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST: | |||||
return parseHost(item, request); | |||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER: | |||||
return parseHeader(item, request); | |||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM: | |||||
return parseUrlParameter(item, request); | |||||
default: | |||||
return null; | |||||
} | |||||
} | |||||
private String parseClientIp(/*@Valid*/ GatewayParamFlowItem item, T request) { | |||||
String clientIp = requestItemParser.getRemoteAddress(request); | |||||
String pattern = item.getPattern(); | |||||
if (pattern == null) { | |||||
return clientIp; | |||||
} | |||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), clientIp, pattern); | |||||
} | |||||
private String parseHeader(/*@Valid*/ GatewayParamFlowItem item, T request) { | |||||
String headerKey = item.getFieldName(); | |||||
String pattern = item.getPattern(); | |||||
// TODO: what if the header has multiple values? | |||||
String headerValue = requestItemParser.getHeader(request, headerKey); | |||||
if (pattern == null) { | |||||
return headerValue; | |||||
} | |||||
// Match value according to regex pattern or exact mode. | |||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), headerValue, pattern); | |||||
} | |||||
private String parseHost(/*@Valid*/ GatewayParamFlowItem item, T request) { | |||||
String pattern = item.getPattern(); | |||||
String host = requestItemParser.getHeader(request, "Host"); | |||||
if (pattern == null) { | |||||
return host; | |||||
} | |||||
// Match value according to regex pattern or exact mode. | |||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), host, pattern); | |||||
} | |||||
private String parseUrlParameter(/*@Valid*/ GatewayParamFlowItem item, T request) { | |||||
String paramName = item.getFieldName(); | |||||
String pattern = item.getPattern(); | |||||
String param = requestItemParser.getUrlParam(request, paramName); | |||||
if (pattern == null) { | |||||
return param; | |||||
} | |||||
// Match value according to regex pattern or exact mode. | |||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern); | |||||
} | |||||
private String parseWithMatchStrategyInternal(int matchStrategy, String value, String pattern) { | |||||
// TODO: implement here. | |||||
if (value == null) { | |||||
return null; | |||||
} | |||||
if (matchStrategy == SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX) { | |||||
return value; | |||||
} | |||||
return value; | |||||
} | |||||
} |
@@ -0,0 +1,57 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.param; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public interface RequestItemParser<T> { | |||||
/** | |||||
* Get API path from the request. | |||||
* | |||||
* @param request valid request | |||||
* @return API path | |||||
*/ | |||||
String getPath(T request); | |||||
/** | |||||
* Get remote address from the request. | |||||
* | |||||
* @param request valid request | |||||
* @return remote address | |||||
*/ | |||||
String getRemoteAddress(T request); | |||||
/** | |||||
* Get the header associated with the header key. | |||||
* | |||||
* @param request valid request | |||||
* @param key valid header key | |||||
* @return the header | |||||
*/ | |||||
String getHeader(T request, String key); | |||||
/** | |||||
* Get the parameter value associated with the parameter name. | |||||
* | |||||
* @param request valid request | |||||
* @param paramName valid parameter name | |||||
* @return the parameter value | |||||
*/ | |||||
String getUrlParam(T request, String paramName); | |||||
} |
@@ -0,0 +1,186 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.rule; | |||||
import java.util.Objects; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public class GatewayFlowRule { | |||||
private String resource; | |||||
private int resourceMode = SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID; | |||||
private int grade = RuleConstant.FLOW_GRADE_QPS; | |||||
private double count; | |||||
private long intervalSec = 1; | |||||
private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT; | |||||
private int burst; | |||||
/** | |||||
* For throttle (rate limiting with queueing). | |||||
*/ | |||||
private int maxQueueingTimeoutMs = 500; | |||||
/** | |||||
* For parameter flow control. If not set, the gateway rule will be | |||||
* converted to normal flow rule. | |||||
*/ | |||||
private GatewayParamFlowItem paramItem; | |||||
public GatewayFlowRule() {} | |||||
public GatewayFlowRule(String resource) { | |||||
this.resource = resource; | |||||
} | |||||
public String getResource() { | |||||
return resource; | |||||
} | |||||
public GatewayFlowRule setResource(String resource) { | |||||
this.resource = resource; | |||||
return this; | |||||
} | |||||
public int getResourceMode() { | |||||
return resourceMode; | |||||
} | |||||
public GatewayFlowRule setResourceMode(int resourceMode) { | |||||
this.resourceMode = resourceMode; | |||||
return this; | |||||
} | |||||
public int getGrade() { | |||||
return grade; | |||||
} | |||||
public GatewayFlowRule setGrade(int grade) { | |||||
this.grade = grade; | |||||
return this; | |||||
} | |||||
public int getControlBehavior() { | |||||
return controlBehavior; | |||||
} | |||||
public GatewayFlowRule setControlBehavior(int controlBehavior) { | |||||
this.controlBehavior = controlBehavior; | |||||
return this; | |||||
} | |||||
public double getCount() { | |||||
return count; | |||||
} | |||||
public GatewayFlowRule setCount(double count) { | |||||
this.count = count; | |||||
return this; | |||||
} | |||||
public long getIntervalSec() { | |||||
return intervalSec; | |||||
} | |||||
public GatewayFlowRule setIntervalSec(long intervalSec) { | |||||
this.intervalSec = intervalSec; | |||||
return this; | |||||
} | |||||
public int getBurst() { | |||||
return burst; | |||||
} | |||||
public GatewayFlowRule setBurst(int burst) { | |||||
this.burst = burst; | |||||
return this; | |||||
} | |||||
public GatewayParamFlowItem getParamItem() { | |||||
return paramItem; | |||||
} | |||||
public GatewayFlowRule setParamItem(GatewayParamFlowItem paramItem) { | |||||
this.paramItem = paramItem; | |||||
return this; | |||||
} | |||||
public int getMaxQueueingTimeoutMs() { | |||||
return maxQueueingTimeoutMs; | |||||
} | |||||
public GatewayFlowRule setMaxQueueingTimeoutMs(int maxQueueingTimeoutMs) { | |||||
this.maxQueueingTimeoutMs = maxQueueingTimeoutMs; | |||||
return this; | |||||
} | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) { return true; } | |||||
if (o == null || getClass() != o.getClass()) { return false; } | |||||
GatewayFlowRule rule = (GatewayFlowRule)o; | |||||
if (resourceMode != rule.resourceMode) { return false; } | |||||
if (grade != rule.grade) { return false; } | |||||
if (Double.compare(rule.count, count) != 0) { return false; } | |||||
if (intervalSec != rule.intervalSec) { return false; } | |||||
if (controlBehavior != rule.controlBehavior) { return false; } | |||||
if (burst != rule.burst) { return false; } | |||||
if (maxQueueingTimeoutMs != rule.maxQueueingTimeoutMs) { return false; } | |||||
if (!Objects.equals(resource, rule.resource)) { return false; } | |||||
return Objects.equals(paramItem, rule.paramItem); | |||||
} | |||||
@Override | |||||
public int hashCode() { | |||||
int result; | |||||
long temp; | |||||
result = resource != null ? resource.hashCode() : 0; | |||||
result = 31 * result + resourceMode; | |||||
result = 31 * result + grade; | |||||
temp = Double.doubleToLongBits(count); | |||||
result = 31 * result + (int)(temp ^ (temp >>> 32)); | |||||
result = 31 * result + (int)(intervalSec ^ (intervalSec >>> 32)); | |||||
result = 31 * result + controlBehavior; | |||||
result = 31 * result + burst; | |||||
result = 31 * result + maxQueueingTimeoutMs; | |||||
result = 31 * result + (paramItem != null ? paramItem.hashCode() : 0); | |||||
return result; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "GatewayFlowRule{" + | |||||
"resource='" + resource + '\'' + | |||||
", resourceMode=" + resourceMode + | |||||
", grade=" + grade + | |||||
", count=" + count + | |||||
", intervalSec=" + intervalSec + | |||||
", controlBehavior=" + controlBehavior + | |||||
", burst=" + burst + | |||||
", maxQueueingTimeoutMs=" + maxQueueingTimeoutMs + | |||||
", paramItem=" + paramItem + | |||||
'}'; | |||||
} | |||||
} |
@@ -0,0 +1,103 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.rule; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public class GatewayParamFlowItem { | |||||
/** | |||||
* Should be set when applying to parameter flow rules. | |||||
*/ | |||||
private Integer index; | |||||
/** | |||||
* Strategy for parsing item (e.g. client IP, arbitrary headers and URL parameters). | |||||
*/ | |||||
private int parseStrategy; | |||||
/** | |||||
* Field to get (only required for arbitrary headers or URL parameters mode). | |||||
*/ | |||||
private String fieldName; | |||||
/** | |||||
* Matching pattern. If not set, all values will be kept in LRU map. | |||||
*/ | |||||
private String pattern; | |||||
/** | |||||
* Matching strategy for item value. | |||||
*/ | |||||
private int matchStrategy = SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT; | |||||
public Integer getIndex() { | |||||
return index; | |||||
} | |||||
GatewayParamFlowItem setIndex(Integer index) { | |||||
this.index = index; | |||||
return this; | |||||
} | |||||
public int getParseStrategy() { | |||||
return parseStrategy; | |||||
} | |||||
public GatewayParamFlowItem setParseStrategy(int parseStrategy) { | |||||
this.parseStrategy = parseStrategy; | |||||
return this; | |||||
} | |||||
public String getFieldName() { | |||||
return fieldName; | |||||
} | |||||
public GatewayParamFlowItem setFieldName(String fieldName) { | |||||
this.fieldName = fieldName; | |||||
return this; | |||||
} | |||||
public String getPattern() { | |||||
return pattern; | |||||
} | |||||
public GatewayParamFlowItem setPattern(String pattern) { | |||||
this.pattern = pattern; | |||||
return this; | |||||
} | |||||
public int getMatchStrategy() { | |||||
return matchStrategy; | |||||
} | |||||
public GatewayParamFlowItem setMatchStrategy(int matchStrategy) { | |||||
this.matchStrategy = matchStrategy; | |||||
return this; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "GatewayParamFlowItem{" + | |||||
"index=" + index + | |||||
", parseStrategy=" + parseStrategy + | |||||
", fieldName='" + fieldName + '\'' + | |||||
", pattern='" + pattern + '\'' + | |||||
", matchStrategy=" + matchStrategy + | |||||
'}'; | |||||
} | |||||
} |
@@ -0,0 +1,60 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.rule; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
final class GatewayRuleConverter { | |||||
static FlowRule toFlowRule(/*@Valid*/ GatewayFlowRule rule) { | |||||
return new FlowRule(rule.getResource()) | |||||
.setControlBehavior(rule.getControlBehavior()) | |||||
.setCount(rule.getCount()) | |||||
.setGrade(rule.getGrade()) | |||||
.setMaxQueueingTimeMs(rule.getMaxQueueingTimeoutMs()); | |||||
} | |||||
/** | |||||
* Convert a gateway rule to parameter flow rule, then apply the generated | |||||
* parameter index to {@link GatewayParamFlowItem} of the rule. | |||||
* | |||||
* @param gatewayRule a valid gateway rule that should contain valid parameter items | |||||
* @param idx generated parameter index (callers should guarantee it's unique and incremental) | |||||
* @return converted parameter flow rule | |||||
*/ | |||||
static ParamFlowRule applyToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) { | |||||
ParamFlowRule paramRule = new ParamFlowRule(gatewayRule.getResource()) | |||||
.setCount(gatewayRule.getCount()) | |||||
.setGrade(gatewayRule.getGrade()) | |||||
.setDurationInSec(gatewayRule.getIntervalSec()) | |||||
.setBurstCount(gatewayRule.getBurst()) | |||||
.setControlBehavior(gatewayRule.getControlBehavior()) | |||||
.setMaxQueueingTimeMs(gatewayRule.getMaxQueueingTimeoutMs()) | |||||
.setParamIdx(idx); | |||||
GatewayParamFlowItem gatewayItem = gatewayRule.getParamItem(); | |||||
// Apply the current idx to gateway rule item. | |||||
gatewayItem.setIndex(idx); | |||||
// TODO: implement for pattern-based parameters. | |||||
return paramRule; | |||||
} | |||||
private GatewayRuleConverter() {} | |||||
} |
@@ -0,0 +1,187 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.rule; | |||||
import java.util.ArrayList; | |||||
import java.util.HashMap; | |||||
import java.util.HashSet; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.Set; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; | |||||
import com.alibaba.csp.sentinel.property.PropertyListener; | |||||
import com.alibaba.csp.sentinel.property.SentinelProperty; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; | |||||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.0 | |||||
*/ | |||||
public final class GatewayRuleManager { | |||||
private static final Map<String, Set<GatewayFlowRule>> RULE_MAP = new ConcurrentHashMap<>(); | |||||
private static final GatewayRulePropertyListener LISTENER = new GatewayRulePropertyListener(); | |||||
private static SentinelProperty<Set<GatewayFlowRule>> currentProperty = new DynamicSentinelProperty<>(); | |||||
static { | |||||
currentProperty.addListener(LISTENER); | |||||
} | |||||
public static void register2Property(SentinelProperty<Set<GatewayFlowRule>> property) { | |||||
AssertUtil.notNull(property, "property cannot be null"); | |||||
synchronized (LISTENER) { | |||||
RecordLog.info("[GatewayRuleManager] Registering new property to gateway flow rule manager"); | |||||
currentProperty.removeListener(LISTENER); | |||||
property.addListener(LISTENER); | |||||
currentProperty = property; | |||||
} | |||||
} | |||||
/** | |||||
* Load all provided gateway rules into memory, while | |||||
* previous rules will be replaced. | |||||
* | |||||
* @param rules rule set | |||||
* @return true if updated, otherwise false | |||||
*/ | |||||
public static boolean loadRules(Set<GatewayFlowRule> rules) { | |||||
return currentProperty.updateValue(rules); | |||||
} | |||||
public static Set<GatewayFlowRule> getRules() { | |||||
Set<GatewayFlowRule> rules = new HashSet<>(); | |||||
for (Set<GatewayFlowRule> ruleSet : RULE_MAP.values()) { | |||||
rules.addAll(ruleSet); | |||||
} | |||||
return rules; | |||||
} | |||||
public static Set<GatewayFlowRule> getRulesForResource(String resourceName) { | |||||
AssertUtil.assertNotBlank(resourceName, "resourceName cannot be blank"); | |||||
Set<GatewayFlowRule> set = RULE_MAP.get(resourceName); | |||||
if (set == null) { | |||||
return new HashSet<>(); | |||||
} | |||||
return new HashSet<>(set); | |||||
} | |||||
private static final class GatewayRulePropertyListener implements PropertyListener<Set<GatewayFlowRule>> { | |||||
@Override | |||||
public void configUpdate(Set<GatewayFlowRule> conf) { | |||||
applyGatewayRuleInternal(conf); | |||||
RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + RULE_MAP); | |||||
} | |||||
@Override | |||||
public void configLoad(Set<GatewayFlowRule> conf) { | |||||
applyGatewayRuleInternal(conf); | |||||
RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: " + RULE_MAP); | |||||
} | |||||
private synchronized void applyGatewayRuleInternal(Set<GatewayFlowRule> conf) { | |||||
if (conf == null || conf.isEmpty()) { | |||||
FlowRuleManager.loadRules(new ArrayList<FlowRule>()); | |||||
ParamFlowRuleManager.loadRules(new ArrayList<ParamFlowRule>()); | |||||
RULE_MAP.clear(); | |||||
return; | |||||
} | |||||
Map<String, Set<GatewayFlowRule>> gatewayRuleMap = new ConcurrentHashMap<>(); | |||||
Map<String, Integer> idxMap = new HashMap<>(); | |||||
List<FlowRule> flowRules = new ArrayList<>(); | |||||
Set<ParamFlowRule> paramFlowRules = new HashSet<>(); | |||||
for (GatewayFlowRule rule : conf) { | |||||
if (!isValidRule(rule)) { | |||||
RecordLog.warn("[GatewayRuleManager] Ignoring invalid rule when loading new rules: " + rule); | |||||
continue; | |||||
} | |||||
String resourceName = rule.getResource(); | |||||
if (rule.getParamItem() == null) { | |||||
// If param item is absent, it will be converted to normal flow rule. | |||||
flowRules.add(GatewayRuleConverter.toFlowRule(rule)); | |||||
} else { | |||||
// Prepare index map. | |||||
if (!idxMap.containsKey(resourceName)) { | |||||
idxMap.put(resourceName, 0); | |||||
} | |||||
int idx = idxMap.get(resourceName); | |||||
// Convert to parameter flow rule. | |||||
if (paramFlowRules.add(GatewayRuleConverter.applyToParamRule(rule, idx))) { | |||||
idxMap.put(rule.getResource(), idx + 1); | |||||
} | |||||
} | |||||
// Apply to the gateway rule map. | |||||
Set<GatewayFlowRule> ruleSet = gatewayRuleMap.get(resourceName); | |||||
if (ruleSet == null) { | |||||
ruleSet = new HashSet<>(); | |||||
gatewayRuleMap.put(resourceName, ruleSet); | |||||
} | |||||
ruleSet.add(rule); | |||||
} | |||||
FlowRuleManager.loadRules(flowRules); | |||||
ParamFlowRuleManager.loadRules(new ArrayList<>(paramFlowRules)); | |||||
RULE_MAP.clear(); | |||||
RULE_MAP.putAll(gatewayRuleMap); | |||||
} | |||||
} | |||||
public static boolean isValidRule(GatewayFlowRule rule) { | |||||
if (rule == null || StringUtil.isBlank(rule.getResource()) || rule.getResourceMode() < 0 | |||||
|| rule.getGrade() < 0 || rule.getCount() < 0 || rule.getBurst() < 0 || rule.getControlBehavior() < 0) { | |||||
return false; | |||||
} | |||||
if (rule.getGrade() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER | |||||
&& rule.getMaxQueueingTimeoutMs() < 0) { | |||||
return false; | |||||
} | |||||
if (rule.getIntervalSec() <= 0) { | |||||
return false; | |||||
} | |||||
GatewayParamFlowItem item = rule.getParamItem(); | |||||
if (item != null) { | |||||
return isValidParamItem(item); | |||||
} | |||||
return true; | |||||
} | |||||
static boolean isValidParamItem(/*@NonNull*/ GatewayParamFlowItem item) { | |||||
if (item.getParseStrategy() < 0) { | |||||
return false; | |||||
} | |||||
if (item.getParseStrategy() == SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM || | |||||
item.getParseStrategy() == SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) { | |||||
if (StringUtil.isBlank(item.getFieldName())) { | |||||
return false; | |||||
} | |||||
} | |||||
return StringUtil.isEmpty(item.getPattern()) || item.getMatchStrategy() >= 0; | |||||
} | |||||
private GatewayRuleManager() {} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
package com.alibaba.csp.sentinel.adapter.gateway.common.api; | |||||
import java.util.Collections; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.*; | |||||
/** | |||||
* @author Eric Zhao | |||||
*/ | |||||
public class GatewayApiDefinitionManagerTest { | |||||
@Test | |||||
public void testIsValidApi() { | |||||
ApiDefinition bad1 = new ApiDefinition(); | |||||
ApiDefinition bad2 = new ApiDefinition("foo"); | |||||
ApiDefinition good1 = new ApiDefinition("foo") | |||||
.setPredicateItems(Collections.<ApiPredicateItem>singleton(new ApiPathPredicateItem() | |||||
.setPattern("/abc") | |||||
)); | |||||
assertFalse(GatewayApiDefinitionManager.isValidApi(bad1)); | |||||
assertFalse(GatewayApiDefinitionManager.isValidApi(bad2)); | |||||
assertTrue(GatewayApiDefinitionManager.isValidApi(good1)); | |||||
} | |||||
} |
@@ -0,0 +1,64 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.rule; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.*; | |||||
/** | |||||
* @author Eric Zhao | |||||
*/ | |||||
public class GatewayRuleConverterTest { | |||||
@Test | |||||
public void testConvertToFlowRule() { | |||||
GatewayFlowRule rule = new GatewayFlowRule("routeId1") | |||||
.setCount(10) | |||||
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) | |||||
.setMaxQueueingTimeoutMs(1000); | |||||
FlowRule flowRule = GatewayRuleConverter.toFlowRule(rule); | |||||
assertEquals(rule.getResource(), flowRule.getResource()); | |||||
assertEquals(rule.getCount(), flowRule.getCount(), 0.01); | |||||
assertEquals(rule.getControlBehavior(), flowRule.getControlBehavior()); | |||||
assertEquals(rule.getMaxQueueingTimeoutMs(), flowRule.getMaxQueueingTimeMs()); | |||||
} | |||||
@Test | |||||
public void testConvertAndApplyToParamRule() { | |||||
GatewayFlowRule routeRule1 = new GatewayFlowRule("routeId1") | |||||
.setCount(2) | |||||
.setIntervalSec(2) | |||||
.setBurst(2) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) | |||||
); | |||||
int idx = 1; | |||||
ParamFlowRule paramRule = GatewayRuleConverter.applyToParamRule(routeRule1, idx); | |||||
assertEquals(routeRule1.getResource(), paramRule.getResource()); | |||||
assertEquals(routeRule1.getCount(), paramRule.getCount(), 0.01); | |||||
assertEquals(routeRule1.getControlBehavior(), paramRule.getControlBehavior()); | |||||
assertEquals(routeRule1.getIntervalSec(), paramRule.getDurationInSec()); | |||||
assertEquals(routeRule1.getBurst(), paramRule.getBurstCount()); | |||||
assertEquals(idx, (int)paramRule.getParamIdx()); | |||||
assertEquals(idx, (int)routeRule1.getParamItem().getIndex()); | |||||
} | |||||
} |
@@ -0,0 +1,115 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* 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.alibaba.csp.sentinel.adapter.gateway.common.rule; | |||||
import java.util.HashSet; | |||||
import java.util.Set; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; | |||||
import org.junit.After; | |||||
import org.junit.Before; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.*; | |||||
/** | |||||
* @author Eric Zhao | |||||
*/ | |||||
public class GatewayRuleManagerTest { | |||||
@Test | |||||
public void testLoadAndGetGatewayRules() { | |||||
Set<GatewayFlowRule> rules = new HashSet<>(); | |||||
String ahasRoute = "ahas_route"; | |||||
GatewayFlowRule rule1 = new GatewayFlowRule(ahasRoute) | |||||
.setCount(500) | |||||
.setIntervalSec(1); | |||||
GatewayFlowRule rule2 = new GatewayFlowRule(ahasRoute) | |||||
.setCount(20) | |||||
.setIntervalSec(2) | |||||
.setBurst(5) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) | |||||
); | |||||
GatewayFlowRule rule3 = new GatewayFlowRule("complex_route_ZZZ") | |||||
.setCount(10) | |||||
.setIntervalSec(1) | |||||
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) | |||||
.setMaxQueueingTimeoutMs(600) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) | |||||
.setFieldName("X-Sentinel-Flag") | |||||
); | |||||
rules.add(rule1); | |||||
rules.add(rule2); | |||||
rules.add(rule3); | |||||
GatewayRuleManager.loadRules(rules); | |||||
assertTrue(FlowRuleManager.hasConfig(ahasRoute)); | |||||
assertTrue(ParamFlowRuleManager.hasRules(ahasRoute)); | |||||
assertEquals(0, (int)rule2.getParamItem().getIndex()); | |||||
assertEquals(0, (int)rule3.getParamItem().getIndex()); | |||||
assertTrue(GatewayRuleManager.getRulesForResource(ahasRoute).contains(rule1)); | |||||
assertTrue(GatewayRuleManager.getRulesForResource(ahasRoute).contains(rule2)); | |||||
} | |||||
@Test | |||||
public void testIsValidRule() { | |||||
GatewayFlowRule bad1 = new GatewayFlowRule(); | |||||
GatewayFlowRule bad2 = new GatewayFlowRule("abc") | |||||
.setIntervalSec(0); | |||||
GatewayFlowRule bad3 = new GatewayFlowRule("abc") | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)); | |||||
GatewayFlowRule bad4 = new GatewayFlowRule("abc") | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) | |||||
.setFieldName("p") | |||||
.setPattern("def") | |||||
.setMatchStrategy(-1) | |||||
); | |||||
GatewayFlowRule good1 = new GatewayFlowRule("abc"); | |||||
GatewayFlowRule good2 = new GatewayFlowRule("abc") | |||||
.setParamItem(new GatewayParamFlowItem().setParseStrategy(0)); | |||||
GatewayFlowRule good3 = new GatewayFlowRule("abc") | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setMatchStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) | |||||
.setFieldName("Origin") | |||||
.setPattern("def")); | |||||
assertFalse(GatewayRuleManager.isValidRule(bad1)); | |||||
assertFalse(GatewayRuleManager.isValidRule(bad2)); | |||||
assertFalse(GatewayRuleManager.isValidRule(bad3)); | |||||
assertFalse(GatewayRuleManager.isValidRule(bad4)); | |||||
assertTrue(GatewayRuleManager.isValidRule(good1)); | |||||
assertTrue(GatewayRuleManager.isValidRule(good2)); | |||||
assertTrue(GatewayRuleManager.isValidRule(good3)); | |||||
} | |||||
@Before | |||||
public void setUp() { | |||||
GatewayRuleManager.loadRules(new HashSet<GatewayFlowRule>()); | |||||
} | |||||
@After | |||||
public void tearDown() { | |||||
GatewayRuleManager.loadRules(new HashSet<GatewayFlowRule>()); | |||||
} | |||||
} |