This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

View File

@@ -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

View File

@@ -0,0 +1,46 @@
<?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.8.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
<packaging>jar</packaging>
<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>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,52 @@
/*
* 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_PARSE_STRATEGY_COOKIE = 4;
public static final int URL_MATCH_STRATEGY_EXACT = 0;
public static final int URL_MATCH_STRATEGY_PREFIX = 1;
public static final int URL_MATCH_STRATEGY_REGEX = 2;
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 int PARAM_MATCH_STRATEGY_CONTAINS = 3;
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 = "$NM";
public static final String GATEWAY_DEFAULT_PARAM = "$D";
private SentinelGatewayConstants() {}
}

View File

@@ -0,0 +1,81 @@
/*
* 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;
/**
* A group of HTTP API patterns.
*
* @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 +
'}';
}
}

View File

@@ -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);
}

View File

@@ -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
*
* 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 com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
/**
* @author Eric Zhao
* @since 1.6.0
*/
public class ApiPathPredicateItem implements ApiPredicateItem {
private String pattern;
private int matchStrategy = SentinelGatewayConstants.URL_MATCH_STRATEGY_EXACT;
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 +
'}';
}
}

View File

@@ -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);
}*/
}

View File

@@ -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);
}*/
}

View File

@@ -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.spi.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.of(ApiDefinitionChangeObserver.class).loadInstanceList();
for (ApiDefinitionChangeObserver e : listeners) {
API_CHANGE_OBSERVERS.put(e.getClass().getCanonicalName(), e);
RecordLog.info("[GatewayApiDefinitionManager] ApiDefinitionChangeObserver added: {}"
, 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());
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.command;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
import com.alibaba.fastjson.JSON;
/**
* @author Eric Zhao
* @since 1.6.0
*/
@CommandMapping(name = "gateway/getApiDefinitions", desc = "Fetch all customized gateway API groups")
public class GetGatewayApiDefinitionGroupCommandHandler implements CommandHandler<String> {
@Override
public CommandResponse<String> handle(CommandRequest request) {
return CommandResponse.ofSuccess(JSON.toJSONString(GatewayApiDefinitionManager.getApiDefinitions()));
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.command;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
import com.alibaba.fastjson.JSON;
/**
* @author Eric Zhao
* @since 1.6.0
*/
@CommandMapping(name = "gateway/getRules", desc = "Fetch all gateway rules")
public class GetGatewayRuleCommandHandler implements CommandHandler<String> {
@Override
public CommandResponse<String> handle(CommandRequest request) {
return CommandResponse.ofSuccess(JSON.toJSONString(GatewayRuleManager.getRules()));
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.command;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.net.URLDecoder;
import java.util.HashSet;
import java.util.Set;
/**
* @author Eric Zhao
* @since 1.6.0
*/
@CommandMapping(name = "gateway/updateApiDefinitions", desc = "")
public class UpdateGatewayApiDefinitionGroupCommandHandler implements CommandHandler<String> {
private static WritableDataSource<Set<ApiDefinition>> apiDefinitionWds = null;
@Override
public CommandResponse<String> handle(CommandRequest request) {
String data = request.getParam("data");
if (StringUtil.isBlank(data)) {
return CommandResponse.ofFailure(new IllegalArgumentException("Bad data"));
}
try {
data = URLDecoder.decode(data, "utf-8");
} catch (Exception e) {
RecordLog.info("Decode gateway API definition data error", e);
return CommandResponse.ofFailure(e, "decode gateway API definition data error");
}
RecordLog.info("[API Server] Receiving data change (type: gateway API definition): {}", data);
String result = SUCCESS_MSG;
Set<ApiDefinition> apiDefinitions = parseJson(data);
GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions);
if (!writeToDataSource(apiDefinitionWds, apiDefinitions)) {
result = WRITE_DS_FAILURE_MSG;
}
return CommandResponse.ofSuccess(result);
}
private static final String SUCCESS_MSG = "success";
private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)";
/**
* Parse json data to set of {@link ApiDefinition}.
*
* Since the predicateItems of {@link ApiDefinition} is set of interface,
* here we parse predicateItems to {@link ApiPathPredicateItem} temporarily.
*/
private Set<ApiDefinition> parseJson(String data) {
Set<ApiDefinition> apiDefinitions = new HashSet<>();
JSONArray array = JSON.parseArray(data);
for (Object obj : array) {
JSONObject o = (JSONObject)obj;
ApiDefinition apiDefinition = new ApiDefinition((o.getString("apiName")));
Set<ApiPredicateItem> predicateItems = new HashSet<>();
JSONArray itemArray = o.getJSONArray("predicateItems");
if (itemArray != null) {
predicateItems.addAll(itemArray.toJavaList(ApiPathPredicateItem.class));
}
apiDefinition.setPredicateItems(predicateItems);
apiDefinitions.add(apiDefinition);
}
return apiDefinitions;
}
/**
* Write target value to given data source.
*
* @param dataSource writable data source
* @param value target value to save
* @param <T> value type
* @return true if write successful or data source is empty; false if error occurs
*/
private <T> boolean writeToDataSource(WritableDataSource<T> dataSource, T value) {
if (dataSource != null) {
try {
dataSource.write(value);
} catch (Exception e) {
RecordLog.warn("Write data source failed", e);
return false;
}
}
return true;
}
public synchronized static WritableDataSource<Set<ApiDefinition>> getWritableDataSource() {
return apiDefinitionWds;
}
public synchronized static void setWritableDataSource(WritableDataSource<Set<ApiDefinition>> apiDefinitionWds) {
UpdateGatewayApiDefinitionGroupCommandHandler.apiDefinitionWds = apiDefinitionWds;
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.command;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import java.net.URLDecoder;
import java.util.Set;
/**
* @author Eric Zhao
* @since 1.6.0
*/
@CommandMapping(name = "gateway/updateRules", desc = "Update gateway rules")
public class UpdateGatewayRuleCommandHandler implements CommandHandler<String> {
private static WritableDataSource<Set<GatewayFlowRule>> gatewayFlowWds = null;
@Override
public CommandResponse<String> handle(CommandRequest request) {
String data = request.getParam("data");
if (StringUtil.isBlank(data)) {
return CommandResponse.ofFailure(new IllegalArgumentException("Bad data"));
}
try {
data = URLDecoder.decode(data, "utf-8");
} catch (Exception e) {
RecordLog.info("Decode gateway rule data error", e);
return CommandResponse.ofFailure(e, "decode gateway rule data error");
}
RecordLog.info("[API Server] Receiving rule change (type: gateway rule): {}", data);
String result = SUCCESS_MSG;
Set<GatewayFlowRule> flowRules = JSON.parseObject(data, new TypeReference<Set<GatewayFlowRule>>() {
});
GatewayRuleManager.loadRules(flowRules);
if (!writeToDataSource(gatewayFlowWds, flowRules)) {
result = WRITE_DS_FAILURE_MSG;
}
return CommandResponse.ofSuccess(result);
}
/**
* Write target value to given data source.
*
* @param dataSource writable data source
* @param value target value to save
* @param <T> value type
* @return true if write successful or data source is empty; false if error occurs
*/
private <T> boolean writeToDataSource(WritableDataSource<T> dataSource, T value) {
if (dataSource != null) {
try {
dataSource.write(value);
} catch (Exception e) {
RecordLog.warn("Write data source failed", e);
return false;
}
}
return true;
}
public synchronized static WritableDataSource<Set<GatewayFlowRule>> getWritableDataSource() {
return gatewayFlowWds;
}
public synchronized static void setWritableDataSource(WritableDataSource<Set<GatewayFlowRule>> gatewayFlowWds) {
UpdateGatewayRuleCommandHandler.gatewayFlowWds = gatewayFlowWds;
}
private static final String SUCCESS_MSG = "success";
private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)";
}

View File

@@ -0,0 +1,175 @@
/*
* 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 java.util.regex.Pattern;
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<>();
boolean hasNonParamRule = false;
for (GatewayFlowRule rule : GatewayRuleManager.getRulesForResource(resource)) {
if (rule.getParamItem() != null) {
gatewayRules.add(rule);
predSet.add(rulePredicate.test(rule));
} else {
hasNonParamRule = true;
}
}
if (!hasNonParamRule && gatewayRules.isEmpty()) {
return new Object[0];
}
if (predSet.size() > 1 || predSet.contains(false)) {
return new Object[0];
}
int size = hasNonParamRule ? gatewayRules.size() + 1 : gatewayRules.size();
Object[] arr = new Object[size];
for (GatewayFlowRule rule : gatewayRules) {
GatewayParamFlowItem paramItem = rule.getParamItem();
int idx = paramItem.getIndex();
String param = parseInternal(paramItem, request);
arr[idx] = param;
}
if (hasNonParamRule) {
arr[size - 1] = SentinelGatewayConstants.GATEWAY_DEFAULT_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);
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE:
return parseCookie(item, request);
default:
return null;
}
}
private String parseClientIp(/*@Valid*/ GatewayParamFlowItem item, T request) {
String clientIp = requestItemParser.getRemoteAddress(request);
String pattern = item.getPattern();
if (StringUtil.isEmpty(pattern)) {
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 (StringUtil.isEmpty(pattern)) {
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 (StringUtil.isEmpty(pattern)) {
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 (StringUtil.isEmpty(pattern)) {
return param;
}
// Match value according to regex pattern or exact mode.
return parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern);
}
private String parseCookie(/*@Valid*/ GatewayParamFlowItem item, T request) {
String cookieName = item.getFieldName();
String pattern = item.getPattern();
String param = requestItemParser.getCookieValue(request, cookieName);
if (StringUtil.isEmpty(pattern)) {
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) {
if (value == null) {
return null;
}
switch (matchStrategy) {
case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT:
return value.equals(pattern) ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS:
return value.contains(pattern) ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX:
Pattern regex = GatewayRegexCache.getRegexPattern(pattern);
if (regex == null) {
return value;
}
return regex.matcher(value).matches() ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
default:
return value;
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import com.alibaba.csp.sentinel.log.RecordLog;
/**
* @author Eric Zhao
* @since 1.6.2
*/
public final class GatewayRegexCache {
private static final Map<String, Pattern> REGEX_CACHE = new ConcurrentHashMap<>();
public static Pattern getRegexPattern(String pattern) {
if (pattern == null) {
return null;
}
return REGEX_CACHE.get(pattern);
}
public static boolean addRegexPattern(String pattern) {
if (pattern == null) {
return false;
}
try {
Pattern regex = Pattern.compile(pattern);
REGEX_CACHE.put(pattern, regex);
return true;
} catch (Exception ex) {
RecordLog.warn("[GatewayRegexCache] Failed to compile the regex: " + pattern, ex);
return false;
}
}
public static void clear() {
REGEX_CACHE.clear();
}
private GatewayRegexCache() {}
}

View File

@@ -0,0 +1,67 @@
/*
* 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);
/**
* Get the cookie value associated with the cookie name.
*
* @param request valid request
* @param cookieName valid cookie name
* @return the cookie value
* @since 1.7.0
*/
String getCookieValue(T request, String cookieName);
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
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());
}
static ParamFlowItem generateNonMatchPassParamItem() {
return new ParamFlowItem().setClassType(String.class.getName())
.setCount(1000_0000)
.setObject(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
}
static ParamFlowItem generateNonMatchBlockParamItem() {
return new ParamFlowItem().setClassType(String.class.getName())
.setCount(0)
.setObject(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
}
static ParamFlowRule applyNonParamToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) {
return new ParamFlowRule(gatewayRule.getResource())
.setCount(gatewayRule.getCount())
.setGrade(gatewayRule.getGrade())
.setDurationInSec(gatewayRule.getIntervalSec())
.setBurstCount(gatewayRule.getBurst())
.setControlBehavior(gatewayRule.getControlBehavior())
.setMaxQueueingTimeMs(gatewayRule.getMaxQueueingTimeoutMs())
.setParamIdx(idx);
}
/**
* 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);
// Apply for pattern-based parameters.
String valuePattern = gatewayItem.getPattern();
if (valuePattern != null) {
paramRule.getParamFlowItemList().add(generateNonMatchPassParamItem());
}
return paramRule;
}
private GatewayRuleConverter() {}
}

View File

@@ -0,0 +1,277 @@
/*
* 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 com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayRegexCache;
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.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleUtil;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Eric Zhao
* @since 1.6.0
*/
public final class GatewayRuleManager {
/**
* Gateway flow rule map: (resource, [rules...])
*/
private static final Map<String, Set<GatewayFlowRule>> GATEWAY_RULE_MAP = new ConcurrentHashMap<>();
private static final Map<String, List<ParamFlowRule>> CONVERTED_PARAM_RULE_MAP = new ConcurrentHashMap<>();
private static final GatewayRulePropertyListener LISTENER = new GatewayRulePropertyListener();
private static final Set<Integer> FIELD_REQUIRED_SET = new HashSet<>(
Arrays.asList(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM,
SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER,
SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE)
);
private static SentinelProperty<Set<GatewayFlowRule>> currentProperty = new DynamicSentinelProperty<>();
static {
currentProperty.addListener(LISTENER);
}
private GatewayRuleManager() {
}
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 : GATEWAY_RULE_MAP.values()) {
rules.addAll(ruleSet);
}
return rules;
}
public static Set<GatewayFlowRule> getRulesForResource(String resourceName) {
if (StringUtil.isBlank(resourceName)) {
return new HashSet<>();
}
Set<GatewayFlowRule> set = GATEWAY_RULE_MAP.get(resourceName);
if (set == null) {
return new HashSet<>();
}
return new HashSet<>(set);
}
/**
* <p>Get all converted parameter rules.</p>
* <p>Note: caller SHOULD NOT modify the list and rules.</p>
*
* @param resourceName valid resource name
* @return converted parameter rules
*/
public static List<ParamFlowRule> getConvertedParamRules(String resourceName) {
if (StringUtil.isBlank(resourceName)) {
return new ArrayList<>();
}
return CONVERTED_PARAM_RULE_MAP.get(resourceName);
}
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;
}
// Check required field name for item types.
if (FIELD_REQUIRED_SET.contains(item.getParseStrategy()) && StringUtil.isBlank(item.getFieldName())) {
return false;
}
return StringUtil.isEmpty(item.getPattern()) || item.getMatchStrategy() >= 0;
}
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: {}", GATEWAY_RULE_MAP);
}
@Override
public void configLoad(Set<GatewayFlowRule> conf) {
applyGatewayRuleInternal(conf);
RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: {}", GATEWAY_RULE_MAP);
}
private int getIdxInternal(Map<String, Integer> idxMap, String resourceName) {
// Prepare index map.
if (!idxMap.containsKey(resourceName)) {
idxMap.put(resourceName, 0);
}
return idxMap.get(resourceName);
}
private void cacheRegexPattern(/*@NonNull*/ GatewayParamFlowItem item) {
String pattern = item.getPattern();
if (StringUtil.isNotEmpty(pattern) &&
item.getMatchStrategy() == SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX) {
if (GatewayRegexCache.getRegexPattern(pattern) == null) {
GatewayRegexCache.addRegexPattern(pattern);
}
}
}
private synchronized void applyGatewayRuleInternal(Set<GatewayFlowRule> conf) {
if (conf == null || conf.isEmpty()) {
applyToConvertedParamMap(new HashSet<ParamFlowRule>());
GATEWAY_RULE_MAP.clear();
return;
}
Map<String, Set<GatewayFlowRule>> gatewayRuleMap = new ConcurrentHashMap<>();
Map<String, Integer> idxMap = new HashMap<>();
Set<ParamFlowRule> paramFlowRules = new HashSet<>();
Map<String, List<GatewayFlowRule>> noParamMap = new HashMap<>();
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) {
// Cache the rules with no parameter config, then skip.
List<GatewayFlowRule> noParamList = noParamMap.get(resourceName);
if (noParamList == null) {
noParamList = new ArrayList<>();
noParamMap.put(resourceName, noParamList);
}
noParamList.add(rule);
} else {
int idx = getIdxInternal(idxMap, resourceName);
// Convert to parameter flow rule.
if (paramFlowRules.add(GatewayRuleConverter.applyToParamRule(rule, idx))) {
idxMap.put(rule.getResource(), idx + 1);
}
cacheRegexPattern(rule.getParamItem());
}
// 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);
}
// Handle non-param mode rules.
for (Map.Entry<String, List<GatewayFlowRule>> e : noParamMap.entrySet()) {
List<GatewayFlowRule> rules = e.getValue();
if (rules == null || rules.isEmpty()) {
continue;
}
for (GatewayFlowRule rule : rules) {
int idx = getIdxInternal(idxMap, e.getKey());
// Always use the same index (the last position).
paramFlowRules.add(GatewayRuleConverter.applyNonParamToParamRule(rule, idx));
}
}
applyToConvertedParamMap(paramFlowRules);
GATEWAY_RULE_MAP.clear();
GATEWAY_RULE_MAP.putAll(gatewayRuleMap);
}
private void applyToConvertedParamMap(Set<ParamFlowRule> paramFlowRules) {
Map<String, List<ParamFlowRule>> newRuleMap = ParamFlowRuleUtil.buildParamRuleMap(
new ArrayList<>(paramFlowRules));
if (newRuleMap == null || newRuleMap.isEmpty()) {
// No parameter flow rules, so clear all the metrics.
for (String resource : CONVERTED_PARAM_RULE_MAP.keySet()) {
ParameterMetricStorage.clearParamMetricForResource(resource);
}
RecordLog.info("[GatewayRuleManager] No gateway rules, clearing parameter metrics of previous rules");
CONVERTED_PARAM_RULE_MAP.clear();
return;
}
// Clear unused parameter metrics.
for (Map.Entry<String, List<ParamFlowRule>> entry : CONVERTED_PARAM_RULE_MAP.entrySet()) {
String resource = entry.getKey();
if (!newRuleMap.containsKey(resource)) {
ParameterMetricStorage.clearParamMetricForResource(resource);
continue;
}
List<ParamFlowRule> newRuleList = newRuleMap.get(resource);
List<ParamFlowRule> oldRuleList = new ArrayList<>(entry.getValue());
oldRuleList.removeAll(newRuleList);
for (ParamFlowRule rule : oldRuleList) {
ParameterMetric metric = ParameterMetricStorage.getParamMetricForResource(resource);
if (null != metric) {
metric.clearForRule(rule);
}
}
}
// Apply to converted rule map.
CONVERTED_PARAM_RULE_MAP.clear();
CONVERTED_PARAM_RULE_MAP.putAll(newRuleMap);
RecordLog.info("[GatewayRuleManager] Converted internal param rules: {}", CONVERTED_PARAM_RULE_MAP);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.slot;
import java.util.List;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage;
import com.alibaba.csp.sentinel.spi.Spi;
/**
* @author Eric Zhao
* @since 1.6.1
*/
@Spi(order = -4000)
public class GatewayFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@Override
public void entry(Context context, ResourceWrapper resource, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
checkGatewayParamFlow(resource, count, args);
fireEntry(context, resource, node, count, prioritized, args);
}
private void checkGatewayParamFlow(ResourceWrapper resourceWrapper, int count, Object... args)
throws BlockException {
if (args == null) {
return;
}
List<ParamFlowRule> rules = GatewayRuleManager.getConvertedParamRules(resourceWrapper.getName());
if (rules == null || rules.isEmpty()) {
return;
}
for (ParamFlowRule rule : rules) {
// Initialize the parameter metrics.
ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule);
if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) {
String triggeredParam = "";
if (args.length > rule.getParamIdx()) {
Object value = args[rule.getParamIdx()];
triggeredParam = String.valueOf(value);
}
throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule);
}
}
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
fireExit(context, resourceWrapper, count, args);
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.slot;
import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder;
/**
* @author Eric Zhao
* @since 1.6.1
*
* @deprecated since 1.7.2, we can use @Spi(order = -4000) to adjust the order of {@link GatewayFlowSlot},
* this class is reserved for compatibility with older versions.
*
* @see GatewayFlowSlot
* @see DefaultSlotChainBuilder
*/
@Deprecated
public class GatewaySlotChainBuilder extends DefaultSlotChainBuilder {
}

View File

@@ -0,0 +1,4 @@
com.alibaba.csp.sentinel.adapter.gateway.common.command.UpdateGatewayApiDefinitionGroupCommandHandler
com.alibaba.csp.sentinel.adapter.gateway.common.command.UpdateGatewayRuleCommandHandler
com.alibaba.csp.sentinel.adapter.gateway.common.command.GetGatewayApiDefinitionGroupCommandHandler
com.alibaba.csp.sentinel.adapter.gateway.common.command.GetGatewayRuleCommandHandler

View File

@@ -0,0 +1 @@
com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewayFlowSlot

View File

@@ -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));
}
}

View File

@@ -0,0 +1,349 @@
/*
* 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.param;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
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.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.util.function.Predicate;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Eric Zhao
*/
@SuppressWarnings("unchecked")
public class GatewayParamParserTest {
private final Predicate<GatewayFlowRule> routeIdPredicate = new Predicate<GatewayFlowRule>() {
@Override
public boolean test(GatewayFlowRule e) {
return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID;
}
};
private final Predicate<GatewayFlowRule> apiNamePredicate = new Predicate<GatewayFlowRule>() {
@Override
public boolean test(GatewayFlowRule e) {
return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME;
}
};
@Test
public void testParseParametersNoParamItem() {
RequestItemParser<Object> itemParser = mock(RequestItemParser.class);
GatewayParamParser<Object> parser = new GatewayParamParser<>(itemParser);
// Create a fake request.
Object request = new Object();
// Prepare gateway rules.
Set<GatewayFlowRule> rules = new HashSet<>();
String routeId1 = "my_test_route_A";
rules.add(new GatewayFlowRule(routeId1)
.setCount(5)
.setIntervalSec(1)
);
rules.add(new GatewayFlowRule(routeId1)
.setCount(10)
.setControlBehavior(2)
.setMaxQueueingTimeoutMs(1000)
);
GatewayRuleManager.loadRules(rules);
Object[] params = parser.parseParameterFor(routeId1, request, routeIdPredicate);
assertThat(params.length).isEqualTo(1);
}
@Test
public void testParseParametersWithItems() {
RequestItemParser<Object> itemParser = mock(RequestItemParser.class);
GatewayParamParser<Object> paramParser = new GatewayParamParser<>(itemParser);
// Create a fake request.
Object request = new Object();
// Prepare gateway rules.
Set<GatewayFlowRule> rules = new HashSet<>();
final String routeId1 = "my_test_route_A";
final String api1 = "my_test_route_B";
final String headerName = "X-Sentinel-Flag";
final String paramName = "p";
final String cookieName = "myCookie";
GatewayFlowRule routeRuleNoParam = new GatewayFlowRule(routeId1)
.setCount(10)
.setIntervalSec(10);
GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId1)
.setCount(2)
.setIntervalSec(2)
.setBurst(2)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
);
GatewayFlowRule routeRule2 = new GatewayFlowRule(routeId1)
.setCount(10)
.setIntervalSec(1)
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
.setMaxQueueingTimeoutMs(600)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
.setFieldName(headerName)
);
GatewayFlowRule routeRule3 = new GatewayFlowRule(routeId1)
.setCount(20)
.setIntervalSec(1)
.setBurst(5)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName(paramName)
);
GatewayFlowRule routeRule4 = new GatewayFlowRule(routeId1)
.setCount(120)
.setIntervalSec(10)
.setBurst(30)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST)
);
GatewayFlowRule routeRule5 = new GatewayFlowRule(routeId1)
.setCount(50)
.setIntervalSec(30)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE)
.setFieldName(cookieName)
);
GatewayFlowRule apiRule1 = new GatewayFlowRule(api1)
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
.setCount(5)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName(paramName)
);
rules.add(routeRule1);
rules.add(routeRule2);
rules.add(routeRule3);
rules.add(routeRule4);
rules.add(routeRule5);
rules.add(routeRuleNoParam);
rules.add(apiRule1);
GatewayRuleManager.loadRules(rules);
final String expectedHost = "hello.test.sentinel";
final String expectedAddress = "66.77.88.99";
final String expectedHeaderValue1 = "Sentinel";
final String expectedUrlParamValue1 = "17";
final String expectedCookieValue1 = "Sentinel-Foo";
mockClientHostAddress(itemParser, expectedAddress);
Map<String, String> expectedHeaders = new HashMap<String, String>() {{
put(headerName, expectedHeaderValue1); put("Host", expectedHost);
}};
mockHeaders(itemParser, expectedHeaders);
mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue1);
mockSingleCookie(itemParser, cookieName, expectedCookieValue1);
Object[] params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate);
// Param length should be 6 (5 with parameters, 1 normal flow with generated constant)
assertThat(params.length).isEqualTo(6);
assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(expectedAddress);
assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(expectedHeaderValue1);
assertThat(params[routeRule3.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue1);
assertThat(params[routeRule4.getParamItem().getIndex()]).isEqualTo(expectedHost);
assertThat(params[routeRule5.getParamItem().getIndex()]).isEqualTo(expectedCookieValue1);
assertThat(params[params.length - 1]).isEqualTo(SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM);
assertThat(paramParser.parseParameterFor(api1, request, routeIdPredicate).length).isZero();
String expectedUrlParamValue2 = "fs";
mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue2);
params = paramParser.parseParameterFor(api1, request, apiNamePredicate);
assertThat(params.length).isEqualTo(1);
assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue2);
}
@Test
public void testParseParametersWithEmptyItemPattern() {
RequestItemParser<Object> itemParser = mock(RequestItemParser.class);
GatewayParamParser<Object> paramParser = new GatewayParamParser<>(itemParser);
// Create a fake request.
Object request = new Object();
// Prepare gateway rules.
Set<GatewayFlowRule> rules = new HashSet<>();
final String routeId = "my_test_route_DS(*H";
final String headerName = "X-Sentinel-Flag";
GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId)
.setCount(10)
.setIntervalSec(2)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
.setFieldName(headerName)
.setPattern("")
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT)
);
rules.add(routeRule1);
GatewayRuleManager.loadRules(rules);
mockSingleHeader(itemParser, headerName, "Sent1nel");
Object[] params = paramParser.parseParameterFor(routeId, request, routeIdPredicate);
assertThat(params.length).isEqualTo(1);
// Empty pattern should not take effect.
assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo("Sent1nel");
}
@Test
public void testParseParametersWithItemPatternMatching() {
RequestItemParser<Object> itemParser = mock(RequestItemParser.class);
GatewayParamParser<Object> paramParser = new GatewayParamParser<>(itemParser);
// Create a fake request.
Object request = new Object();
// Prepare gateway rules.
Set<GatewayFlowRule> rules = new HashSet<>();
final String routeId1 = "my_test_route_F&@";
final String api1 = "my_test_route_E5K";
final String headerName = "X-Sentinel-Flag";
final String paramName = "p";
String nameEquals = "Wow";
String nameContains = "warn";
String valueRegex = "\\d+";
GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId1)
.setCount(10)
.setIntervalSec(1)
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
.setMaxQueueingTimeoutMs(600)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
.setFieldName(headerName)
.setPattern(nameEquals)
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT)
);
GatewayFlowRule routeRule2 = new GatewayFlowRule(routeId1)
.setCount(20)
.setIntervalSec(1)
.setBurst(5)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName(paramName)
.setPattern(nameContains)
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS)
);
GatewayFlowRule apiRule1 = new GatewayFlowRule(api1)
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
.setCount(5)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName(paramName)
.setPattern(valueRegex)
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX)
);
rules.add(routeRule1);
rules.add(routeRule2);
rules.add(apiRule1);
GatewayRuleManager.loadRules(rules);
mockSingleHeader(itemParser, headerName, nameEquals);
mockSingleUrlParam(itemParser, paramName, nameContains);
Object[] params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate);
assertThat(params.length).isEqualTo(2);
assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(nameEquals);
assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(nameContains);
mockSingleHeader(itemParser, headerName, nameEquals + "_foo");
mockSingleUrlParam(itemParser, paramName, nameContains + "_foo");
params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate);
assertThat(params.length).isEqualTo(2);
assertThat(params[routeRule1.getParamItem().getIndex()])
.isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
assertThat(params[routeRule2.getParamItem().getIndex()])
.isEqualTo(nameContains + "_foo");
mockSingleHeader(itemParser, headerName, "foo");
mockSingleUrlParam(itemParser, paramName, "foo");
params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate);
assertThat(params.length).isEqualTo(2);
assertThat(params[routeRule1.getParamItem().getIndex()])
.isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
assertThat(params[routeRule2.getParamItem().getIndex()])
.isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
mockSingleUrlParam(itemParser, paramName, "23");
params = paramParser.parseParameterFor(api1, request, apiNamePredicate);
assertThat(params.length).isEqualTo(1);
assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo("23");
mockSingleUrlParam(itemParser, paramName, "some233");
params = paramParser.parseParameterFor(api1, request, apiNamePredicate);
assertThat(params.length).isEqualTo(1);
assertThat(params[apiRule1.getParamItem().getIndex()])
.isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
}
private void mockClientHostAddress(/*@Mock*/ RequestItemParser parser, String address) {
when(parser.getRemoteAddress(any())).thenReturn(address);
}
private void mockHeaders(/*@Mock*/ RequestItemParser parser, Map<String, String> headerMap) {
for (Map.Entry<String, String> e : headerMap.entrySet()) {
when(parser.getHeader(any(), eq(e.getKey()))).thenReturn(e.getValue());
}
}
private void mockUrlParams(/*@Mock*/ RequestItemParser parser, Map<String, String> paramMap) {
for (Map.Entry<String, String> e : paramMap.entrySet()) {
when(parser.getUrlParam(any(), eq(e.getKey()))).thenReturn(e.getValue());
}
}
private void mockSingleUrlParam(/*@Mock*/ RequestItemParser parser, String key, String value) {
when(parser.getUrlParam(any(), eq(key))).thenReturn(value);
}
private void mockSingleHeader(/*@Mock*/ RequestItemParser parser, String key, String value) {
when(parser.getHeader(any(), eq(key))).thenReturn(value);
}
private void mockSingleCookie(/*@Mock*/ RequestItemParser parser, String key, String value) {
when(parser.getCookieValue(any(), eq(key))).thenReturn(value);
}
@Before
public void setUp() {
GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<ApiDefinition>());
GatewayRuleManager.loadRules(new HashSet<GatewayFlowRule>());
GatewayRegexCache.clear();
}
@After
public void tearDown() {
GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<ApiDefinition>());
GatewayRuleManager.loadRules(new HashSet<GatewayFlowRule>());
GatewayRegexCache.clear();
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.param;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Eric Zhao
*/
public class GatewayRegexCacheTest {
@Before
public void setUp() {
GatewayRegexCache.clear();
}
@After
public void tearDown() {
GatewayRegexCache.clear();
}
@Test
public void testAddAndGetRegexPattern() {
// Test for invalid pattern.
assertThat(GatewayRegexCache.addRegexPattern("\\")).isFalse();
assertThat(GatewayRegexCache.addRegexPattern(null)).isFalse();
// Test for good pattern.
String goodPattern = "\\d+";
assertThat(GatewayRegexCache.addRegexPattern(goodPattern)).isTrue();
assertThat(GatewayRegexCache.getRegexPattern(goodPattern)).isNotNull();
}
}

View File

@@ -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());
}
}

View File

@@ -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.List;
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.param.ParamFlowRule;
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);
List<ParamFlowRule> convertedRules = GatewayRuleManager.getConvertedParamRules(ahasRoute);
assertNotNull(convertedRules);
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>());
}
}