set = GATEWAY_RULE_MAP.get(resourceName);
if (set == null) {
return new HashSet<>();
}
return new HashSet<>(set);
}
+ /**
+ * Get all converted parameter rules.
+ * Note: caller SHOULD NOT modify the list and rules.
+ *
+ * @param resourceName valid resource name
+ * @return converted parameter rules
+ */
+ public static List getConvertedParamRules(String resourceName) {
+ if (StringUtil.isBlank(resourceName)) {
+ return new ArrayList<>();
+ }
+ return CONVERTED_PARAM_RULE_MAP.get(resourceName);
+ }
+
private static final class GatewayRulePropertyListener implements PropertyListener> {
@Override
public void configUpdate(Set conf) {
applyGatewayRuleInternal(conf);
- RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + RULE_MAP);
+ RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + GATEWAY_RULE_MAP);
}
@Override
public void configLoad(Set conf) {
applyGatewayRuleInternal(conf);
- RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: " + RULE_MAP);
+ RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: " + GATEWAY_RULE_MAP);
+ }
+
+ private int getIdxInternal(Map idxMap, String resourceName) {
+ // Prepare index map.
+ if (!idxMap.containsKey(resourceName)) {
+ idxMap.put(resourceName, 0);
+ }
+ return idxMap.get(resourceName);
}
private synchronized void applyGatewayRuleInternal(Set conf) {
if (conf == null || conf.isEmpty()) {
- FlowRuleManager.loadRules(new ArrayList());
- ParamFlowRuleManager.loadRules(new ArrayList());
- RULE_MAP.clear();
+ applyToConvertedParamMap(new HashSet());
+ GATEWAY_RULE_MAP.clear();
return;
}
Map> gatewayRuleMap = new ConcurrentHashMap<>();
Map idxMap = new HashMap<>();
- List flowRules = new ArrayList<>();
Set paramFlowRules = new HashSet<>();
+ Map> noParamMap = new HashMap<>();
for (GatewayFlowRule rule : conf) {
if (!isValidRule(rule)) {
@@ -122,14 +149,15 @@ public final class GatewayRuleManager {
}
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);
+ // Cache the rules with no parameter config, then skip.
+ List noParamList = noParamMap.get(resourceName);
+ if (noParamList == null) {
+ noParamList = new ArrayList<>();
+ noParamMap.put(resourceName, noParamList);
}
- int idx = idxMap.get(resourceName);
+ 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);
@@ -143,11 +171,51 @@ public final class GatewayRuleManager {
}
ruleSet.add(rule);
}
- FlowRuleManager.loadRules(flowRules);
- ParamFlowRuleManager.loadRules(new ArrayList<>(paramFlowRules));
+ // Handle non-param mode rules.
+ for (Map.Entry> e : noParamMap.entrySet()) {
+ List 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));
+ }
+ }
- RULE_MAP.clear();
- RULE_MAP.putAll(gatewayRuleMap);
+ applyToConvertedParamMap(paramFlowRules);
+
+ GATEWAY_RULE_MAP.clear();
+ GATEWAY_RULE_MAP.putAll(gatewayRuleMap);
+ }
+
+ private void applyToConvertedParamMap(Set paramFlowRules) {
+ Map> 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.
+ Set previousResources = CONVERTED_PARAM_RULE_MAP.keySet();
+ for (String resource : previousResources) {
+ if (!newRuleMap.containsKey(resource)) {
+ ParameterMetricStorage.clearParamMetricForResource(resource);
+ }
+ }
+
+ // 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);
}
}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java
new file mode 100644
index 00000000..15151729
--- /dev/null
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.1
+ */
+public class GatewayFlowSlot extends AbstractLinkedProcessorSlot {
+
+ @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 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);
+ }
+}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java
new file mode 100644
index 00000000..383e4653
--- /dev/null
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java
@@ -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.slot;
+
+import com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain;
+import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
+import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder;
+import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot;
+import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot;
+import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
+import com.alibaba.csp.sentinel.slots.logger.LogSlot;
+import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot;
+import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot;
+import com.alibaba.csp.sentinel.slots.system.SystemSlot;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.1
+ */
+public class GatewaySlotChainBuilder implements SlotChainBuilder {
+
+ @Override
+ public ProcessorSlotChain build() {
+ ProcessorSlotChain chain = new DefaultProcessorSlotChain();
+ // Prepare slot
+ chain.addLast(new NodeSelectorSlot());
+ chain.addLast(new ClusterBuilderSlot());
+ // Stat slot
+ chain.addLast(new LogSlot());
+ chain.addLast(new StatisticSlot());
+ // Rule checking slot
+ chain.addLast(new AuthoritySlot());
+ chain.addLast(new SystemSlot());
+ chain.addLast(new GatewayFlowSlot());
+
+ chain.addLast(new ParamFlowSlot());
+ chain.addLast(new FlowSlot());
+ chain.addLast(new DegradeSlot());
+
+ return chain;
+ }
+}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder
new file mode 100644
index 00000000..5f9fde56
--- /dev/null
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder
@@ -0,0 +1 @@
+com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewaySlotChainBuilder
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParserTest.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParserTest.java
new file mode 100644
index 00000000..9fc8dc0e
--- /dev/null
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParserTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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 routeIdPredicate = new Predicate() {
+ @Override
+ public boolean test(GatewayFlowRule e) {
+ return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID;
+ }
+ };
+ private final Predicate apiNamePredicate = new Predicate() {
+ @Override
+ public boolean test(GatewayFlowRule e) {
+ return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME;
+ }
+ };
+
+ @Test
+ public void testParseParametersNoParamItem() {
+ RequestItemParser