ソースを参照

Add Sentinel Spring Cloud Gateway adapter module and implementation

Signed-off-by: Eric Zhao <sczyh16@gmail.com>
master
Eric Zhao 5年前
コミット
0f875d89f3
20個のファイルの変更1220行の追加0行の削除
  1. +1
    -0
      sentinel-adapter/pom.xml
  2. +54
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/README.md
  3. +82
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/pom.xml
  4. +90
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilter.java
  5. +53
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/ServerWebExchangeItemParser.java
  6. +65
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/GatewayApiMatcherManager.java
  7. +33
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/SpringCloudGatewayApiDefinitionChangeObserver.java
  8. +70
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/matcher/WebExchangeApiMatcher.java
  9. +38
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/BlockRequestHandler.java
  10. +93
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/DefaultBlockRequestHandler.java
  11. +68
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/GatewayCallbackManager.java
  12. +43
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/RedirectBlockRequestHandler.java
  13. +78
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/exception/SentinelGatewayBlockExceptionHandler.java
  14. +55
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/AntRoutePathMatcher.java
  15. +49
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RegexRoutePathMatcher.java
  16. +46
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RouteMatchers.java
  17. +1
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver
  18. +91
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilterTest.java
  19. +209
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SpringCloudGatewayParamParserTest.java
  20. +1
    -0
      sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

+ 1
- 0
sentinel-adapter/pom.xml ファイルの表示

@@ -23,6 +23,7 @@
<module>sentinel-reactor-adapter</module>
<module>sentinel-spring-webflux-adapter</module>
<module>sentinel-api-gateway-adapter-common</module>
<module>sentinel-spring-cloud-gateway-adapter</module>
</modules>

<dependencyManagement>


+ 54
- 0
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
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>x.y.z</version>
</dependency>
```

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<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;

public GatewayConfiguration(ObjectProvider<List<ViewResolver>> 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`.

+ 82
- 0
sentinel-adapter/sentinel-spring-cloud-gateway-adapter/pom.xml ファイルの表示

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sentinel-adapter</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>1.6.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<packaging>jar</packaging>

<properties>
<java.source.version>1.8</java.source.version>
<java.target.version>1.8</java.target.version>
<spring.cloud.gateway.version>2.1.1.RELEASE</spring.cloud.gateway.version>
<spring.boot.version>2.1.4.RELEASE</spring.boot.version>
<spring.version>5.1.5.RELEASE</spring.version>
</properties>

<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-reactor-adapter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-core</artifactId>
<version>${spring.cloud.gateway.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>${spring.cloud.gateway.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</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>

+ 90
- 0
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<ServerWebExchange> paramParser = new GatewayParamParser<>(
new ServerWebExchangeItemParser());

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

Mono<Void> 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<String> 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<String> pickMatchingApiDefinitions(ServerWebExchange exchange) {
return GatewayApiMatcherManager.getApiMatcherMap().values()
.stream()
.filter(m -> m.test(exchange))
.map(WebExchangeApiMatcher::getApiName)
.collect(Collectors.toSet());
}
}

+ 53
- 0
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<ServerWebExchange> {

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

+ 65
- 0
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<String, WebExchangeApiMatcher> API_MATCHER_MAP = new ConcurrentHashMap<>();

public static Map<String, WebExchangeApiMatcher> getApiMatcherMap() {
return Collections.unmodifiableMap(API_MATCHER_MAP);
}

public static Optional<WebExchangeApiMatcher> getMatcher(final String apiName) {
return Optional.ofNullable(apiName)
.map(e -> API_MATCHER_MAP.get(apiName));
}

public static Set<ApiDefinition> getApiDefinitionSet() {
return API_MATCHER_MAP.values()
.stream()
.map(WebExchangeApiMatcher::getApiDefinition)
.collect(Collectors.toSet());
}

static synchronized void loadApiDefinitions(/*@Valid*/ Set<ApiDefinition> 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() {}
}

+ 33
- 0
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<ApiDefinition> apiDefinitions) {
GatewayApiMatcherManager.loadApiDefinitions(apiDefinitions);
}
}

+ 70
- 0
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<ServerWebExchange> {

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<Predicate<ServerWebExchange>> fromApiPredicate(/*@NonNull*/ ApiPredicateItem item) {
if (item instanceof ApiPathPredicateItem) {
return fromApiPathPredicate((ApiPathPredicateItem)item);
}
return Optional.empty();
}

private Optional<Predicate<ServerWebExchange>> 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));
}
}
}

+ 38
- 0
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<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t);
}

+ 93
- 0
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<ServerResponse> 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<ServerResponse> 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<MediaType> 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;
}
}
}

+ 68
- 0
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<ServerWebExchange, String> DEFAULT_ORIGIN_PARSER = (w) -> "";

/**
* BlockRequestHandler: (serverExchange, exception) -> response
*/
private static volatile BlockRequestHandler blockHandler = new DefaultBlockRequestHandler();
/**
* RequestOriginParser: (serverExchange) -> origin
*/
private static volatile Function<ServerWebExchange, String> 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<ServerWebExchange, String> getRequestOriginParser() {
return requestOriginParser;
}

public static void resetRequestOriginParser() {
GatewayCallbackManager.requestOriginParser = DEFAULT_ORIGIN_PARSER;
}

public static void setRequestOriginParser(Function<ServerWebExchange, String> requestOriginParser) {
AssertUtil.notNull(requestOriginParser, "requestOriginParser cannot be null");
GatewayCallbackManager.requestOriginParser = requestOriginParser;
}

private GatewayCallbackManager() {}
}

+ 43
- 0
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<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
return ServerResponse.temporaryRedirect(uri).build();
}
}

+ 78
- 0
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<ViewResolver> viewResolvers;
private List<HttpMessageWriter<?>> messageWriters;

public SentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolvers;
this.messageWriters = serverCodecConfigurer.getWriters();
}

private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
return response.writeTo(exchange, contextSupplier.get());
}

@Override
public Mono<Void> 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<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}

private final Supplier<ServerResponse.Context> contextSupplier = () -> new ServerResponse.Context() {
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return SentinelGatewayBlockExceptionHandler.this.messageWriters;
}

@Override
public List<ViewResolver> viewResolvers() {
return SentinelGatewayBlockExceptionHandler.this.viewResolvers;
}
};
}

+ 55
- 0
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<ServerWebExchange> {

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

+ 49
- 0
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<ServerWebExchange> {

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

+ 46
- 0
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<ServerWebExchange> all() {
return exchange -> true;
}

public static Predicate<ServerWebExchange> antPath(String pathPattern) {
return new AntRoutePathMatcher(pathPattern);
}

public static Predicate<ServerWebExchange> exactPath(final String path) {
return exchange -> exchange.getRequest().getPath().value().equals(path);
}

public static Predicate<ServerWebExchange> regexPath(String pathPattern) {
return new RegexRoutePathMatcher(pathPattern);
}

private RouteMatchers() {}
}

+ 1
- 0
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

+ 91
- 0
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<ApiDefinition> 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<ApiPredicateItem>() {{
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<String> 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<>());
}
}

+ 209
- 0
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<ServerWebExchange> paramParser = new GatewayParamParser<>(
new ServerWebExchangeItemParser()
);

@Test
public void testParseParametersNoParamItem() {
// Mock a request.
ServerWebExchange exchange = mock(ServerWebExchange.class);
// Prepare gateway rules.
Set<GatewayFlowRule> 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<GatewayFlowRule> 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<String, String> expectedHeaders = new HashMap<String, String>() {{
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<String, String> headerMap) {
HttpHeaders headers = mock(HttpHeaders.class);
when(request.getHeaders()).thenReturn(headers);
for (Map.Entry<String, String> e : headerMap.entrySet()) {
when(headers.getFirst(e.getKey())).thenReturn(e.getValue());
}
}

@SuppressWarnings("unchecked")
private void mockUrlParams(/*@Mock*/ ServerHttpRequest request, Map<String, String> paramMap) {
MultiValueMap<String, String> urlParams = mock(MultiValueMap.class);
when(request.getQueryParams()).thenReturn(urlParams);
for (Map.Entry<String, String> e : paramMap.entrySet()) {
when(urlParams.getFirst(e.getKey())).thenReturn(e.getValue());
}
}

@SuppressWarnings("unchecked")
private void mockSingleUrlParam(/*@Mock*/ ServerHttpRequest request, String key, String value) {
MultiValueMap<String, String> 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<>());
}
}

+ 1
- 0
sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker ファイルの表示

@@ -0,0 +1 @@
mock-maker-inline

読み込み中…
キャンセル
保存