diff --git a/sentinel-adapter/pom.xml b/sentinel-adapter/pom.xml
index 13e516cb..44531d92 100755
--- a/sentinel-adapter/pom.xml
+++ b/sentinel-adapter/pom.xml
@@ -23,6 +23,7 @@
sentinel-reactor-adapter
sentinel-spring-webflux-adapter
sentinel-api-gateway-adapter-common
+ sentinel-spring-cloud-gateway-adapter
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/README.md b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/README.md
new file mode 100644
index 00000000..4961b32f
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/README.md
@@ -0,0 +1,54 @@
+# Sentinel Spring Cloud Gateway Adapter
+
+> Note: this module requires Java 8 or later version.
+
+Sentinel provides integration module with Spring Cloud Gateway.
+The integration module is based on the Sentinel Reactor Adapter.
+
+Add the following dependency in `pom.xml` (if you are using Maven):
+
+```xml
+
+ com.alibaba.csp
+ sentinel-spring-cloud-gateway-adapter
+ x.y.z
+
+```
+
+Then you only need to inject the corresponding `SentinelGatewayFilter` and `SentinelGatewayBlockExceptionHandler` instance
+in Spring configuration. For example:
+
+```java
+@Configuration
+public class GatewayConfiguration {
+
+ private final List viewResolvers;
+ private final ServerCodecConfigurer serverCodecConfigurer;
+
+ public GatewayConfiguration(ObjectProvider> viewResolversProvider,
+ ServerCodecConfigurer serverCodecConfigurer) {
+ this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
+ this.serverCodecConfigurer = serverCodecConfigurer;
+ }
+
+ @Bean
+ @Order(-1)
+ public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
+ // Register the block exception handler for Spring Cloud Gateway.
+ return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
+ }
+
+ @Bean
+ @Order(-1)
+ public GlobalFilter sentinelGatewayFilter() {
+ return new SentinelGatewayFilter();
+ }
+}
+```
+
+The gateway adapter will regard all `routeId` (defined in Spring properties) and all customized API definitions
+(defined in `GatewayApiDefinitionManager` of `sentinel-api-gateway-adapter-common` module) as resources.
+
+You can register various customized callback in `GatewayCallbackManager`:
+
+- `setBlockHandler`: register a customized `BlockRequestHandler` to handle the blocked request. The default implementation is `DefaultBlockRequestHandler`, which returns default message like `Blocked by Sentinel: FlowException`.
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/pom.xml b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/pom.xml
new file mode 100644
index 00000000..73c06db8
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/pom.xml
@@ -0,0 +1,82 @@
+
+
+
+ sentinel-adapter
+ com.alibaba.csp
+ 1.6.0-SNAPSHOT
+
+ 4.0.0
+
+ sentinel-spring-cloud-gateway-adapter
+ jar
+
+
+ 1.8
+ 1.8
+ 2.1.1.RELEASE
+ 2.1.4.RELEASE
+ 5.1.5.RELEASE
+
+
+
+
+ com.alibaba.csp
+ sentinel-api-gateway-adapter-common
+
+
+ com.alibaba.csp
+ sentinel-reactor-adapter
+
+
+
+ org.springframework.cloud
+ spring-cloud-gateway-core
+ ${spring.cloud.gateway.version}
+ provided
+
+
+ org.springframework
+ spring-webflux
+ ${spring.version}
+ provided
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+ ${spring.cloud.gateway.version}
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+ ${spring.boot.version}
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ ${spring.boot.version}
+ test
+
+
+
+ junit
+ junit
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilter.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilter.java
new file mode 100644
index 00000000..dbe9c130
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilter.java
@@ -0,0 +1,90 @@
+/*
+ * 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.sc;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
+import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser;
+import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
+import com.alibaba.csp.sentinel.adapter.reactor.ContextConfig;
+import com.alibaba.csp.sentinel.adapter.reactor.EntryConfig;
+import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer;
+import com.alibaba.csp.sentinel.adapter.gateway.sc.api.GatewayApiMatcherManager;
+import com.alibaba.csp.sentinel.adapter.gateway.sc.api.matcher.WebExchangeApiMatcher;
+
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.cloud.gateway.route.Route;
+import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public class SentinelGatewayFilter implements GatewayFilter, GlobalFilter {
+
+ private final GatewayParamParser paramParser = new GatewayParamParser<>(
+ new ServerWebExchangeItemParser());
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
+
+ Mono asyncResult = chain.filter(exchange);
+ if (route != null) {
+ String routeId = route.getId();
+ Object[] params = paramParser.parseParameterFor(routeId, exchange,
+ r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);
+ String origin = Optional.ofNullable(GatewayCallbackManager.getRequestOriginParser())
+ .map(f -> f.apply(exchange))
+ .orElse("");
+ asyncResult = asyncResult.transform(
+ new SentinelReactorTransformer<>(new EntryConfig(routeId, EntryType.IN,
+ 1, params, new ContextConfig(contextName(routeId), origin)))
+ );
+ }
+
+ Set matchingApis = pickMatchingApiDefinitions(exchange);
+ for (String apiName : matchingApis) {
+ Object[] params = paramParser.parseParameterFor(apiName, exchange,
+ r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME);
+ asyncResult = asyncResult.transform(
+ new SentinelReactorTransformer<>(new EntryConfig(apiName, EntryType.IN, 1, params))
+ );
+ }
+
+ return asyncResult;
+ }
+
+ private String contextName(String route) {
+ return SentinelGatewayConstants.GATEWAY_CONTEXT_ROUTE_PREFIX + route;
+ }
+
+ Set pickMatchingApiDefinitions(ServerWebExchange exchange) {
+ return GatewayApiMatcherManager.getApiMatcherMap().values()
+ .stream()
+ .filter(m -> m.test(exchange))
+ .map(WebExchangeApiMatcher::getApiName)
+ .collect(Collectors.toSet());
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/ServerWebExchangeItemParser.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/ServerWebExchangeItemParser.java
new file mode 100644
index 00000000..6088cd2d
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/ServerWebExchangeItemParser.java
@@ -0,0 +1,53 @@
+/*
+ * 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.sc;
+
+import java.net.InetSocketAddress;
+
+import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser;
+
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public class ServerWebExchangeItemParser implements RequestItemParser {
+
+ @Override
+ public String getPath(ServerWebExchange exchange) {
+ return exchange.getRequest().getPath().value();
+ }
+
+ @Override
+ public String getRemoteAddress(ServerWebExchange exchange) {
+ InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();
+ if (remoteAddress == null) {
+ return null;
+ }
+ return remoteAddress.getAddress().getHostAddress();
+ }
+
+ @Override
+ public String getHeader(ServerWebExchange exchange, String key) {
+ return exchange.getRequest().getHeaders().getFirst(key);
+ }
+
+ @Override
+ public String getUrlParam(ServerWebExchange exchange, String paramName) {
+ return exchange.getRequest().getQueryParams().getFirst(paramName);
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/GatewayApiMatcherManager.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/GatewayApiMatcherManager.java
new file mode 100644
index 00000000..5c1567ca
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/GatewayApiMatcherManager.java
@@ -0,0 +1,65 @@
+/*
+ * 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.sc.api;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
+import com.alibaba.csp.sentinel.adapter.gateway.sc.api.matcher.WebExchangeApiMatcher;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public final class GatewayApiMatcherManager {
+
+ private static final Map API_MATCHER_MAP = new ConcurrentHashMap<>();
+
+ public static Map getApiMatcherMap() {
+ return Collections.unmodifiableMap(API_MATCHER_MAP);
+ }
+
+ public static Optional getMatcher(final String apiName) {
+ return Optional.ofNullable(apiName)
+ .map(e -> API_MATCHER_MAP.get(apiName));
+ }
+
+ public static Set getApiDefinitionSet() {
+ return API_MATCHER_MAP.values()
+ .stream()
+ .map(WebExchangeApiMatcher::getApiDefinition)
+ .collect(Collectors.toSet());
+ }
+
+ static synchronized void loadApiDefinitions(/*@Valid*/ Set definitions) {
+ if (definitions == null || definitions.isEmpty()) {
+ API_MATCHER_MAP.clear();
+ return;
+ }
+ definitions.forEach(GatewayApiMatcherManager::addApiDefinition);
+ }
+
+ static void addApiDefinition(ApiDefinition definition) {
+ API_MATCHER_MAP.put(definition.getApiName(), new WebExchangeApiMatcher(definition));
+ }
+
+ private GatewayApiMatcherManager() {}
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/SpringCloudGatewayApiDefinitionChangeObserver.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/SpringCloudGatewayApiDefinitionChangeObserver.java
new file mode 100644
index 00000000..41781b7c
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/SpringCloudGatewayApiDefinitionChangeObserver.java
@@ -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.sc.api;
+
+import java.util.Set;
+
+import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
+import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public class SpringCloudGatewayApiDefinitionChangeObserver implements ApiDefinitionChangeObserver {
+
+ @Override
+ public void onChange(Set apiDefinitions) {
+ GatewayApiMatcherManager.loadApiDefinitions(apiDefinitions);
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/matcher/WebExchangeApiMatcher.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/matcher/WebExchangeApiMatcher.java
new file mode 100644
index 00000000..7abae52b
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/matcher/WebExchangeApiMatcher.java
@@ -0,0 +1,70 @@
+/*
+ * 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.sc.api.matcher;
+
+import java.util.Optional;
+
+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.ApiPathPredicateItem;
+import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
+import com.alibaba.csp.sentinel.adapter.gateway.common.api.matcher.AbstractApiMatcher;
+import com.alibaba.csp.sentinel.adapter.gateway.sc.route.RouteMatchers;
+import com.alibaba.csp.sentinel.util.StringUtil;
+import com.alibaba.csp.sentinel.util.function.Predicate;
+
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public class WebExchangeApiMatcher extends AbstractApiMatcher {
+
+ public WebExchangeApiMatcher(ApiDefinition apiDefinition) {
+ super(apiDefinition);
+ }
+
+ @Override
+ protected void initializeMatchers() {
+ if (apiDefinition.getPredicateItems() != null) {
+ apiDefinition.getPredicateItems().forEach(item ->
+ fromApiPredicate(item).ifPresent(matchers::add));
+ }
+ }
+
+ private Optional> fromApiPredicate(/*@NonNull*/ ApiPredicateItem item) {
+ if (item instanceof ApiPathPredicateItem) {
+ return fromApiPathPredicate((ApiPathPredicateItem)item);
+ }
+ return Optional.empty();
+ }
+
+ private Optional> fromApiPathPredicate(/*@Valid*/ ApiPathPredicateItem item) {
+ String pattern = item.getPattern();
+ if (StringUtil.isBlank(pattern)) {
+ return Optional.empty();
+ }
+ switch (item.getMatchStrategy()) {
+ case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX:
+ return Optional.of(RouteMatchers.regexPath(pattern));
+ case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX:
+ return Optional.of(RouteMatchers.antPath(pattern));
+ default:
+ return Optional.of(RouteMatchers.exactPath(pattern));
+ }
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/BlockRequestHandler.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/BlockRequestHandler.java
new file mode 100644
index 00000000..180ec19f
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/BlockRequestHandler.java
@@ -0,0 +1,38 @@
+/*
+ * 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.sc.callback;
+
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * Reactive handler for the blocked request.
+ *
+ * @author Eric Zhao
+ */
+@FunctionalInterface
+public interface BlockRequestHandler {
+
+ /**
+ * Handle the blocked request.
+ *
+ * @param exchange server exchange object
+ * @param t block exception
+ * @return server response to return
+ */
+ Mono handleRequest(ServerWebExchange exchange, Throwable t);
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/DefaultBlockRequestHandler.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/DefaultBlockRequestHandler.java
new file mode 100644
index 00000000..32da88c8
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/DefaultBlockRequestHandler.java
@@ -0,0 +1,93 @@
+/*
+ * 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.sc.callback;
+
+import java.util.List;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.InvalidMediaTypeException;
+import org.springframework.http.MediaType;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+
+/**
+ * The default implementation of {@link BlockRequestHandler}.
+ * Compatible with Spring WebFlux and Spring Cloud Gateway.
+ *
+ * @author Eric Zhao
+ */
+public class DefaultBlockRequestHandler implements BlockRequestHandler {
+
+ private static final String DEFAULT_BLOCK_MSG_PREFIX = "Blocked by Sentinel: ";
+
+ @Override
+ public Mono handleRequest(ServerWebExchange exchange, Throwable ex) {
+ if (acceptsHtml(exchange)) {
+ return htmlErrorResponse(ex);
+ }
+ // JSON result by default.
+ return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
+ .contentType(MediaType.APPLICATION_JSON_UTF8)
+ .body(fromObject(buildErrorResult(ex)));
+ }
+
+ private Mono htmlErrorResponse(Throwable ex) {
+ return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
+ .contentType(MediaType.TEXT_PLAIN)
+ .syncBody(DEFAULT_BLOCK_MSG_PREFIX + ex.getClass().getSimpleName());
+ }
+
+ private ErrorResult buildErrorResult(Throwable ex) {
+ return new ErrorResult(HttpStatus.TOO_MANY_REQUESTS.value(),
+ DEFAULT_BLOCK_MSG_PREFIX + ex.getClass().getSimpleName());
+ }
+
+ /**
+ * Reference from {@code DefaultErrorWebExceptionHandler} of Spring Boot.
+ */
+ private boolean acceptsHtml(ServerWebExchange exchange) {
+ try {
+ List acceptedMediaTypes = exchange.getRequest().getHeaders().getAccept();
+ acceptedMediaTypes.remove(MediaType.ALL);
+ MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
+ return acceptedMediaTypes.stream()
+ .anyMatch(MediaType.TEXT_HTML::isCompatibleWith);
+ } catch (InvalidMediaTypeException ex) {
+ return false;
+ }
+ }
+
+ private static class ErrorResult {
+ private final int code;
+ private final String message;
+
+ ErrorResult(int code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/GatewayCallbackManager.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/GatewayCallbackManager.java
new file mode 100644
index 00000000..73c37d56
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/GatewayCallbackManager.java
@@ -0,0 +1,68 @@
+/*
+ * 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.sc.callback;
+
+import java.util.function.Function;
+
+import com.alibaba.csp.sentinel.util.AssertUtil;
+
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public final class GatewayCallbackManager {
+
+ private static final Function DEFAULT_ORIGIN_PARSER = (w) -> "";
+
+ /**
+ * BlockRequestHandler: (serverExchange, exception) -> response
+ */
+ private static volatile BlockRequestHandler blockHandler = new DefaultBlockRequestHandler();
+ /**
+ * RequestOriginParser: (serverExchange) -> origin
+ */
+ private static volatile Function requestOriginParser = DEFAULT_ORIGIN_PARSER;
+
+ public static /*@NonNull*/ BlockRequestHandler getBlockHandler() {
+ return blockHandler;
+ }
+
+ public static void resetBlockHandler() {
+ GatewayCallbackManager.blockHandler = new DefaultBlockRequestHandler();
+ }
+
+ public static void setBlockHandler(BlockRequestHandler blockHandler) {
+ AssertUtil.notNull(blockHandler, "blockHandler cannot be null");
+ GatewayCallbackManager.blockHandler = blockHandler;
+ }
+
+ public static /*@NonNull*/ Function getRequestOriginParser() {
+ return requestOriginParser;
+ }
+
+ public static void resetRequestOriginParser() {
+ GatewayCallbackManager.requestOriginParser = DEFAULT_ORIGIN_PARSER;
+ }
+
+ public static void setRequestOriginParser(Function requestOriginParser) {
+ AssertUtil.notNull(requestOriginParser, "requestOriginParser cannot be null");
+ GatewayCallbackManager.requestOriginParser = requestOriginParser;
+ }
+
+ private GatewayCallbackManager() {}
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/RedirectBlockRequestHandler.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/RedirectBlockRequestHandler.java
new file mode 100644
index 00000000..cc942724
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/RedirectBlockRequestHandler.java
@@ -0,0 +1,43 @@
+/*
+ * 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.sc.callback;
+
+import java.net.URI;
+
+import com.alibaba.csp.sentinel.util.AssertUtil;
+
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public class RedirectBlockRequestHandler implements BlockRequestHandler {
+
+ private final URI uri;
+
+ public RedirectBlockRequestHandler(String url) {
+ AssertUtil.assertNotBlank(url, "url cannot be blank");
+ this.uri = URI.create(url);
+ }
+
+ @Override
+ public Mono handleRequest(ServerWebExchange exchange, Throwable t) {
+ return ServerResponse.temporaryRedirect(uri).build();
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/exception/SentinelGatewayBlockExceptionHandler.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/exception/SentinelGatewayBlockExceptionHandler.java
new file mode 100644
index 00000000..73c139df
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/exception/SentinelGatewayBlockExceptionHandler.java
@@ -0,0 +1,78 @@
+/*
+ * 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.sc.exception;
+
+import java.util.List;
+
+import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.util.function.Supplier;
+
+import org.springframework.http.codec.HttpMessageWriter;
+import org.springframework.http.codec.ServerCodecConfigurer;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.reactive.result.view.ViewResolver;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebExceptionHandler;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public class SentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
+
+ private List viewResolvers;
+ private List> messageWriters;
+
+ public SentinelGatewayBlockExceptionHandler(List viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
+ this.viewResolvers = viewResolvers;
+ this.messageWriters = serverCodecConfigurer.getWriters();
+ }
+
+ private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) {
+ return response.writeTo(exchange, contextSupplier.get());
+ }
+
+ @Override
+ public Mono handle(ServerWebExchange exchange, Throwable ex) {
+ if (exchange.getResponse().isCommitted()) {
+ return Mono.error(ex);
+ }
+ // This exception handler only handles rejection by Sentinel.
+ if (!BlockException.isBlockException(ex)) {
+ return Mono.error(ex);
+ }
+ return handleBlockedRequest(exchange, ex)
+ .flatMap(response -> writeResponse(response, exchange));
+ }
+
+ private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
+ return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
+ }
+
+ private final Supplier contextSupplier = () -> new ServerResponse.Context() {
+ @Override
+ public List> messageWriters() {
+ return SentinelGatewayBlockExceptionHandler.this.messageWriters;
+ }
+
+ @Override
+ public List viewResolvers() {
+ return SentinelGatewayBlockExceptionHandler.this.viewResolvers;
+ }
+ };
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/AntRoutePathMatcher.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/AntRoutePathMatcher.java
new file mode 100644
index 00000000..475373ea
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/AntRoutePathMatcher.java
@@ -0,0 +1,55 @@
+/*
+ * 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.sc.route;
+
+import com.alibaba.csp.sentinel.util.AssertUtil;
+import com.alibaba.csp.sentinel.util.function.Predicate;
+
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public class AntRoutePathMatcher implements Predicate {
+
+ private final String pattern;
+
+ private final PathMatcher pathMatcher;
+ private final boolean canMatch;
+
+ public AntRoutePathMatcher(String pattern) {
+ AssertUtil.assertNotBlank(pattern, "pattern cannot be blank");
+ this.pattern = pattern;
+ this.pathMatcher = new AntPathMatcher();
+ this.canMatch = pathMatcher.isPattern(pattern);
+ }
+
+ @Override
+ public boolean test(ServerWebExchange exchange) {
+ String path = exchange.getRequest().getPath().value();
+ if (canMatch) {
+ return pathMatcher.match(pattern, path);
+ }
+ return false;
+ }
+
+ public String getPattern() {
+ return pattern;
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RegexRoutePathMatcher.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RegexRoutePathMatcher.java
new file mode 100644
index 00000000..03355c8c
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RegexRoutePathMatcher.java
@@ -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.sc.route;
+
+import java.util.regex.Pattern;
+
+import com.alibaba.csp.sentinel.util.AssertUtil;
+import com.alibaba.csp.sentinel.util.function.Predicate;
+
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public class RegexRoutePathMatcher implements Predicate {
+
+ private final String pattern;
+ private final Pattern regex;
+
+ public RegexRoutePathMatcher(String pattern) {
+ AssertUtil.assertNotBlank(pattern, "pattern cannot be blank");
+ this.pattern = pattern;
+ this.regex = Pattern.compile(pattern);
+ }
+
+ @Override
+ public boolean test(ServerWebExchange exchange) {
+ String path = exchange.getRequest().getPath().value();
+ return regex.matcher(path).matches();
+ }
+
+ public String getPattern() {
+ return pattern;
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RouteMatchers.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RouteMatchers.java
new file mode 100644
index 00000000..6f392144
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RouteMatchers.java
@@ -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
+ *
+ * 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.sc.route;
+
+
+import com.alibaba.csp.sentinel.util.function.Predicate;
+
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.0
+ */
+public final class RouteMatchers {
+
+ public static Predicate all() {
+ return exchange -> true;
+ }
+
+ public static Predicate antPath(String pathPattern) {
+ return new AntRoutePathMatcher(pathPattern);
+ }
+
+ public static Predicate exactPath(final String path) {
+ return exchange -> exchange.getRequest().getPath().value().equals(path);
+ }
+
+ public static Predicate regexPath(String pathPattern) {
+ return new RegexRoutePathMatcher(pathPattern);
+ }
+
+ private RouteMatchers() {}
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver
new file mode 100644
index 00000000..bcd8171c
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver
@@ -0,0 +1 @@
+com.alibaba.csp.sentinel.adapter.gateway.sc.api.SpringCloudGatewayApiDefinitionChangeObserver
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilterTest.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilterTest.java
new file mode 100644
index 00000000..078bfec6
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilterTest.java
@@ -0,0 +1,91 @@
+package com.alibaba.csp.sentinel.adapter.gateway.sc;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
+import com.alibaba.csp.sentinel.adapter.gateway.common.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.adapter.gateway.common.rule.GatewayRuleManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.server.RequestPath;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.server.ServerWebExchange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test cases for {@link SentinelGatewayFilter}.
+ *
+ * @author Eric Zhao
+ */
+public class SentinelGatewayFilterTest {
+
+ @Test
+ public void testPickMatchingApiDefinitions() {
+ // Mock a request.
+ ServerWebExchange exchange = mock(ServerWebExchange.class);
+ ServerHttpRequest request = mock(ServerHttpRequest.class);
+ when(exchange.getRequest()).thenReturn(request);
+ RequestPath requestPath = mock(RequestPath.class);
+ when(request.getPath()).thenReturn(requestPath);
+
+ // Prepare API definitions.
+ Set apiDefinitions = new HashSet<>();
+ String apiName1 = "some_customized_api";
+ ApiDefinition api1 = new ApiDefinition(apiName1)
+ .setPredicateItems(Collections.singleton(
+ new ApiPathPredicateItem().setPattern("/product/**")
+ .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX)
+ ));
+ String apiName2 = "another_customized_api";
+ ApiDefinition api2 = new ApiDefinition(apiName2)
+ .setPredicateItems(new HashSet() {{
+ add(new ApiPathPredicateItem().setPattern("/something"));
+ add(new ApiPathPredicateItem().setPattern("/other/**")
+ .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX));
+ }});
+ apiDefinitions.add(api1);
+ apiDefinitions.add(api2);
+ GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions);
+ SentinelGatewayFilter filter = new SentinelGatewayFilter();
+
+ when(requestPath.value()).thenReturn("/product/123");
+ Set matchingApis = filter.pickMatchingApiDefinitions(exchange);
+ assertThat(matchingApis.size()).isEqualTo(1);
+ assertThat(matchingApis.contains(apiName1)).isTrue();
+
+ when(requestPath.value()).thenReturn("/products");
+ assertThat(filter.pickMatchingApiDefinitions(exchange).size()).isZero();
+
+ when(requestPath.value()).thenReturn("/something");
+ matchingApis = filter.pickMatchingApiDefinitions(exchange);
+ assertThat(matchingApis.size()).isEqualTo(1);
+ assertThat(matchingApis.contains(apiName2)).isTrue();
+
+ when(requestPath.value()).thenReturn("/other/foo/3");
+ matchingApis = filter.pickMatchingApiDefinitions(exchange);
+ assertThat(matchingApis.size()).isEqualTo(1);
+ assertThat(matchingApis.contains(apiName2)).isTrue();
+ }
+
+ @Before
+ public void setUp() {
+ GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>());
+ GatewayRuleManager.loadRules(new HashSet<>());
+ }
+
+ @After
+ public void tearDown() {
+ GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>());
+ GatewayRuleManager.loadRules(new HashSet<>());
+ }
+}
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SpringCloudGatewayParamParserTest.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SpringCloudGatewayParamParserTest.java
new file mode 100644
index 00000000..5b9ca980
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SpringCloudGatewayParamParserTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.sc;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+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.GatewayApiDefinitionManager;
+import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser;
+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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.RequestPath;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.server.ServerWebExchange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Eric Zhao
+ */
+public class SpringCloudGatewayParamParserTest {
+
+ private final GatewayParamParser paramParser = new GatewayParamParser<>(
+ new ServerWebExchangeItemParser()
+ );
+
+ @Test
+ public void testParseParametersNoParamItem() {
+ // Mock a request.
+ ServerWebExchange exchange = mock(ServerWebExchange.class);
+ // Prepare gateway rules.
+ Set rules = new HashSet<>();
+ String routeId1 = "my_test_route_A";
+ rules.add(new GatewayFlowRule(routeId1)
+ .setCount(5)
+ .setIntervalSec(1)
+ );
+ GatewayRuleManager.loadRules(rules);
+
+ Object[] params = paramParser.parseParameterFor(routeId1, exchange,
+ e -> e.getResourceMode() == 0);
+ assertThat(params.length).isZero();
+ }
+
+ @Test
+ public void testParseParametersWithItems() {
+ // Mock a request.
+ ServerWebExchange exchange = mock(ServerWebExchange.class);
+ ServerHttpRequest request = mock(ServerHttpRequest.class);
+ when(exchange.getRequest()).thenReturn(request);
+ RequestPath requestPath = mock(RequestPath.class);
+ when(request.getPath()).thenReturn(requestPath);
+
+ // Prepare gateway rules.
+ Set rules = new HashSet<>();
+ String routeId1 = "my_test_route_A";
+ String api1 = "my_test_route_B";
+ String headerName = "X-Sentinel-Flag";
+ String paramName = "p";
+ 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 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(apiRule1);
+ GatewayRuleManager.loadRules(rules);
+
+ String expectedHost = "hello.test.sentinel";
+ String expectedAddress = "66.77.88.99";
+ String expectedHeaderValue1 = "Sentinel";
+ String expectedUrlParamValue1 = "17";
+ mockClientHostAddress(request, expectedAddress);
+ Map expectedHeaders = new HashMap() {{
+ put(headerName, expectedHeaderValue1); put("Host", expectedHost);
+ }};
+ mockHeaders(request, expectedHeaders);
+ mockSingleUrlParam(request, paramName, expectedUrlParamValue1);
+ Object[] params = paramParser.parseParameterFor(routeId1, exchange, e -> e.getResourceMode() == 0);
+ assertThat(params.length).isEqualTo(4);
+ 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(paramParser.parseParameterFor(api1, exchange, e -> e.getResourceMode() == 0).length).isZero();
+
+ String expectedUrlParamValue2 = "fs";
+ mockSingleUrlParam(request, paramName, expectedUrlParamValue2);
+ params = paramParser.parseParameterFor(api1, exchange, e -> e.getResourceMode() == 1);
+ assertThat(params.length).isEqualTo(1);
+ assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue2);
+ }
+
+ private void mockClientHostAddress(/*@Mock*/ ServerHttpRequest request, String address) {
+ InetSocketAddress socketAddress = mock(InetSocketAddress.class);
+ when(request.getRemoteAddress()).thenReturn(socketAddress);
+ InetAddress inetAddress = mock(InetAddress.class);
+ when(inetAddress.getHostAddress()).thenReturn(address);
+ when(socketAddress.getAddress()).thenReturn(inetAddress);
+ }
+
+ private void mockHeaders(/*@Mock*/ ServerHttpRequest request, Map headerMap) {
+ HttpHeaders headers = mock(HttpHeaders.class);
+ when(request.getHeaders()).thenReturn(headers);
+ for (Map.Entry e : headerMap.entrySet()) {
+ when(headers.getFirst(e.getKey())).thenReturn(e.getValue());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void mockUrlParams(/*@Mock*/ ServerHttpRequest request, Map paramMap) {
+ MultiValueMap urlParams = mock(MultiValueMap.class);
+ when(request.getQueryParams()).thenReturn(urlParams);
+ for (Map.Entry e : paramMap.entrySet()) {
+ when(urlParams.getFirst(e.getKey())).thenReturn(e.getValue());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void mockSingleUrlParam(/*@Mock*/ ServerHttpRequest request, String key, String value) {
+ MultiValueMap urlParams = mock(MultiValueMap.class);
+ when(request.getQueryParams()).thenReturn(urlParams);
+ when(urlParams.getFirst(key)).thenReturn(value);
+ }
+
+ private void mockSingleHeader(/*@Mock*/ ServerHttpRequest request, String key, String value) {
+ HttpHeaders headers = mock(HttpHeaders.class);
+ when(request.getHeaders()).thenReturn(headers);
+ when(headers.getFirst(key)).thenReturn(value);
+ }
+
+ @Before
+ public void setUp() {
+ GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>());
+ GatewayRuleManager.loadRules(new HashSet<>());
+ }
+
+ @After
+ public void tearDown() {
+ GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>());
+ GatewayRuleManager.loadRules(new HashSet<>());
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 00000000..ca6ee9ce
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file