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

View File

@@ -0,0 +1,90 @@
# Sentinel Zuul 2.x Adapter
This adapter provides **route level** and **customized API level**
flow control for Zuul 2.x API Gateway.
> *Note*: this adapter only supports Zuul 2.x.
## How to use
> You can refer to demo [`sentinel-demo-zuul2-gateway`](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-zuul2-gateway).
1. Add Maven dependency to your `pom.xml`:
```xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-zuul2-adapter</artifactId>
<version>x.y.z</version>
</dependency>
```
2. Register filters
```java
filterMultibinder.addBinding().toInstance(new SentinelZuulInboundFilter(500));
filterMultibinder.addBinding().toInstance(new SentinelZuulOutboundFilter(500));
filterMultibinder.addBinding().toInstance(new SentinelZuulEndpoint());
```
## How it works
As Zuul 2.x is based on Netty, an event-driven asynchronous model, so we use `AsyncEntry`.
- `SentinelZuulInboundFilter`: This inbound filter will regard all routes (`routeVIP` in `SessionContext` by default) and all customized API as resources. When a `BlockException` caught, the filter will set endpoint to find a fallback to execute.
- `SentinelZuulOutboundFilter`: When the response has no exception caught, the post filter will trace the exception and complete the entries.
- `SentinelZuulEndpoint`: When an exception is caught, the filter will find a fallback to execute.
## Integration with Sentinel Dashboard
1. Start [Sentinel Dashboard](https://github.com/alibaba/Sentinel/wiki/Dashboard).
2. You can configure the rules in Sentinel dashboard or via dynamic rule configuration.
> You may need to add `-Dcsp.sentinel.app.type=1` property to mark this application as API gateway.
## Fallbacks
You can implement `ZuulBlockFallbackProvider` to define your own fallback provider when Sentinel `BlockException` is thrown.
The default fallback provider is `DefaultBlockFallbackProvider`.
By default fallback route is proxy ID (or customized API name).
Here is an example:
```java
// custom provider
public class MyBlockFallbackProvider implements ZuulBlockFallbackProvider {
private Logger logger = LoggerFactory.getLogger(DefaultBlockFallbackProvider.class);
// you can define root as service level
@Override
public String getRoute() {
return "my-route";
}
@Override
public BlockResponse fallbackResponse(String route, Throwable cause) {
RecordLog.info(String.format("[Sentinel DefaultBlockFallbackProvider] Run fallback route: %s", route));
if (cause instanceof BlockException) {
return new BlockResponse(429, "Sentinel block exception", route);
} else {
return new BlockResponse(500, "System Error", route);
}
}
}
// register fallback
ZuulBlockFallbackManager.registerProvider(new MyBlockFallbackProvider());
```
Default block response:
```json
{
"code":429,
"message":"Sentinel block exception",
"route":"/"
}
```

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sentinel-adapter</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>1.8.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-zuul2-adapter</artifactId>
<packaging>jar</packaging>
<properties>
<java.source.version>1.8</java.source.version>
<java.target.version>1.8</java.target.version>
<zuul.version>2.1.5</zuul.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.zuul</groupId>
<artifactId>zuul-core</artifactId>
<version>${zuul.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- The Spring library is introduced for AntMatcher -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.18.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,51 @@
/*
* 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.zuul2;
import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser;
import com.netflix.zuul.message.http.HttpRequestMessage;
/**
* @author wavesZh
* @since 1.7.2
*/
public class HttpRequestMessageItemParser implements RequestItemParser<HttpRequestMessage> {
@Override
public String getPath(HttpRequestMessage request) {
return request.getInboundRequest().getPath();
}
@Override
public String getRemoteAddress(HttpRequestMessage request) {
return request.getOriginalHost();
}
@Override
public String getHeader(HttpRequestMessage request, String key) {
return String.valueOf(request.getInboundRequest().getHeaders().get(key));
}
@Override
public String getUrlParam(HttpRequestMessage request, String paramName) {
return String.valueOf(request.getInboundRequest().getQueryParams().get(paramName));
}
@Override
public String getCookieValue(HttpRequestMessage request, String cookieName) {
return String.valueOf(request.getInboundRequest().parseCookies().get(cookieName));
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 1999-2019 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.gateway.zuul2.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.7.2
*/
public class ZuulApiDefinitionChangeObserver implements ApiDefinitionChangeObserver {
@Override
public void onChange(Set<ApiDefinition> apiDefinitions) {
ZuulGatewayApiMatcherManager.loadApiDefinitions(apiDefinitions);
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.zuul2.api;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.matcher.HttpRequestMessageApiMatcher;
/**
* @author wavesZh
* @since 1.7.2
*/
public final class ZuulGatewayApiMatcherManager {
private static final Map<String, HttpRequestMessageApiMatcher> API_MATCHER_MAP = new ConcurrentHashMap<>();
public static Map<String, HttpRequestMessageApiMatcher> getApiMatcherMap() {
return Collections.unmodifiableMap(API_MATCHER_MAP);
}
public static HttpRequestMessageApiMatcher getMatcher(final String apiName) {
if (apiName == null) {
return null;
}
return API_MATCHER_MAP.get(apiName);
}
public static Set<ApiDefinition> getApiDefinitionSet() {
Set<ApiDefinition> set = new HashSet<>();
for (HttpRequestMessageApiMatcher matcher : API_MATCHER_MAP.values()) {
set.add(matcher.getApiDefinition());
}
return set;
}
static synchronized void loadApiDefinitions(/*@Valid*/ Set<ApiDefinition> definitions) {
if (definitions == null || definitions.isEmpty()) {
API_MATCHER_MAP.clear();
return;
}
for (ApiDefinition definition : definitions) {
addApiDefinition(definition);
}
}
static void addApiDefinition(ApiDefinition definition) {
API_MATCHER_MAP.put(definition.getApiName(), new HttpRequestMessageApiMatcher(definition));
}
private ZuulGatewayApiMatcherManager() {}
}

View File

@@ -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.zuul2.api.matcher;
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.zuul2.api.route.ZuulRouteMatchers;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.util.function.Predicate;
import com.netflix.zuul.message.http.HttpRequestMessage;
/**
* @author wavesZh
*/
public class HttpRequestMessageApiMatcher extends AbstractApiMatcher<HttpRequestMessage> {
public HttpRequestMessageApiMatcher(ApiDefinition apiDefinition) {
super(apiDefinition);
}
@Override
protected void initializeMatchers() {
if (apiDefinition.getPredicateItems() != null) {
for (ApiPredicateItem item : apiDefinition.getPredicateItems()) {
Predicate<HttpRequestMessage> predicate = fromApiPredicate(item);
if (predicate != null) {
matchers.add(predicate);
}
}
}
}
private Predicate<HttpRequestMessage> fromApiPredicate(/*@NonNull*/ ApiPredicateItem item) {
if (item instanceof ApiPathPredicateItem) {
return fromApiPathPredicate((ApiPathPredicateItem)item);
}
return null;
}
private Predicate<HttpRequestMessage> fromApiPathPredicate(/*@Valid*/ ApiPathPredicateItem item) {
String pattern = item.getPattern();
if (StringUtil.isBlank(pattern)) {
return null;
}
switch (item.getMatchStrategy()) {
case SentinelGatewayConstants.URL_MATCH_STRATEGY_REGEX:
return ZuulRouteMatchers.regexPath(pattern);
case SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX:
return ZuulRouteMatchers.antPath(pattern);
default:
return ZuulRouteMatchers.exactPath(pattern);
}
}
}

View File

@@ -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.zuul2.api.route;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.function.Predicate;
import com.netflix.zuul.message.http.HttpRequestMessage;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
/**
* @author wavesZh
*/
public class PrefixRoutePathMatcher implements Predicate<HttpRequestMessage> {
private final String pattern;
private final PathMatcher pathMatcher;
private final boolean canMatch;
public PrefixRoutePathMatcher(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(HttpRequestMessage context) {
String path = context.getPath();
if (canMatch) {
return pathMatcher.match(pattern, path);
}
return false;
}
public String getPattern() {
return pattern;
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.zuul2.api.route;
import java.util.regex.Pattern;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.function.Predicate;
import com.netflix.zuul.message.http.HttpRequestMessage;
/**
* @author wavesZh
*/
public class RegexRoutePathMatcher implements Predicate<HttpRequestMessage> {
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(HttpRequestMessage input) {
String path = input.getInboundRequest().getPath();
return regex.matcher(path).matches();
}
public String getPattern() {
return pattern;
}
}

View File

@@ -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.zuul2.api.route;
import com.alibaba.csp.sentinel.util.function.Predicate;
import com.netflix.zuul.message.http.HttpRequestMessage;
/**
* @author wavesZh
*/
public final class ZuulRouteMatchers {
public static Predicate<HttpRequestMessage> all() {
return requestContext -> true;
}
public static Predicate<HttpRequestMessage> antPath(String pathPattern) {
return new PrefixRoutePathMatcher(pathPattern);
}
public static Predicate<HttpRequestMessage> exactPath(final String path) {
return exchange -> exchange.getPath().equals(path);
}
public static Predicate<HttpRequestMessage> regexPath(String pathPattern) {
return new RegexRoutePathMatcher(pathPattern);
}
private ZuulRouteMatchers() {}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 1999-2018 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.zuul2.constants;
/**
* @author wavesZh
*/
public class SentinelZuul2Constants {
/**
* The default entrance (context) name when the routeId is empty.
*/
public static final String ZUUL_DEFAULT_CONTEXT = "zuul2_default_context";
/**
* Zuul context key for keeping Sentinel entries.
*/
public static final String ZUUL_CTX_SENTINEL_ENTRIES_KEY = "_sentinel_entries";
public static final String ZUUL_CTX_SENTINEL_FALLBACK_ROUTE = "_sentinel_fallback_route";
/**
* Indicate if request is blocked .
*/
public static final String ZUUL_CTX_SENTINEL_BLOCKED_FLAG = "_sentinel_blocked_flag";
private SentinelZuul2Constants() {}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 1999-2018 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.zuul2.fallback;
/**
* Fall back response for {@link com.alibaba.csp.sentinel.slots.block.BlockException}
*
* @author tiger
*/
public class BlockResponse {
/**
* HTTP status code.
*/
private int code;
private String message;
private String route;
public BlockResponse(int code, String message, String route) {
this.code = code;
this.message = message;
this.route = route;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getRoute() {
return route;
}
public void setRoute(String route) {
this.route = route;
}
@Override
public String toString() {
return "{" +
"\"code\":" + code +
", \"message\":" + "\"" + message + "\"" +
", \"route\":" + "\"" + route + "\"" +
'}';
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 1999-2018 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.zuul2.fallback;
import com.alibaba.csp.sentinel.slots.block.BlockException;
/**
* Default fallback provider for Sentinel {@link BlockException}, {@literal *} meant for all routes.
*
* @author tiger
*/
public class DefaultBlockFallbackProvider implements ZuulBlockFallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public BlockResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof BlockException) {
return new BlockResponse(429, "SentinelBlockException", route);
} else {
return new BlockResponse(500, "System Error", route);
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 1999-2018 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.zuul2.fallback;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* This provide fall back class manager.
*
* @author tiger
*/
public class ZuulBlockFallbackManager {
private static Map<String, ZuulBlockFallbackProvider> fallbackProviderCache = new HashMap<>();
private static ZuulBlockFallbackProvider defaultFallbackProvider = new DefaultBlockFallbackProvider();
/**
* Register special provider for different route.
*/
public static synchronized void registerProvider(ZuulBlockFallbackProvider provider) {
AssertUtil.notNull(provider, "fallback provider cannot be null");
String route = provider.getRoute();
if ("*".equals(route) || route == null) {
defaultFallbackProvider = provider;
} else {
fallbackProviderCache.put(route, provider);
}
}
public static ZuulBlockFallbackProvider getFallbackProvider(String route) {
ZuulBlockFallbackProvider provider = fallbackProviderCache.get(route);
if (provider == null) {
provider = defaultFallbackProvider;
}
return provider;
}
public synchronized static void clear(){
fallbackProviderCache.clear();
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 1999-2018 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.zuul2.fallback;
/**
* This interface is compatible for different spring cloud version.
*
* @author tiger
*/
public interface ZuulBlockFallbackProvider {
/**
* The route this fallback will be used for.
* @return The route the fallback will be used for.
*/
String getRoute();
/**
* Provides a fallback response based on the cause of the failed execution.
*
* @param route The route the fallback is for
* @param cause cause of the main method failure, may be <code>null</code>
* @return the fallback response
*/
BlockResponse fallbackResponse(String route, Throwable cause);
}

View File

@@ -0,0 +1,41 @@
/*
* 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.zuul2.filters;
import com.alibaba.csp.sentinel.Entry;
/**
* @author wavesZh
*/
public class EntryHolder {
final private Entry entry;
final private Object[] params;
public EntryHolder(Entry entry, Object[] params) {
this.entry = entry;
this.params = params;
}
public Entry getEntry() {
return entry;
}
public Object[] getParams() {
return params;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 1999-2018 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.zuul2.filters.endpoint;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.SentinelZuul2Constants;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.BlockResponse;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.ZuulBlockFallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.ZuulBlockFallbackProvider;
import com.netflix.zuul.context.SessionContext;
import com.netflix.zuul.filters.http.HttpSyncEndpoint;
import com.netflix.zuul.message.http.HttpRequestMessage;
import com.netflix.zuul.message.http.HttpResponseMessage;
import com.netflix.zuul.message.http.HttpResponseMessageImpl;
/**
* Default Endpoint for handling exception.
*
* @author wavesZh
*/
public class SentinelZuulEndpoint extends HttpSyncEndpoint {
@Override
public HttpResponseMessage apply(HttpRequestMessage request) {
SessionContext context = request.getContext();
Throwable throwable = context.getError();
String fallBackRoute = (String) context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE);
ZuulBlockFallbackProvider zuulBlockFallbackProvider = ZuulBlockFallbackManager
.getFallbackProvider(fallBackRoute);
BlockResponse response = zuulBlockFallbackProvider.fallbackResponse(fallBackRoute, throwable);
HttpResponseMessage resp = new HttpResponseMessageImpl(context, request, response.getCode());
resp.setBodyAsText(response.toString());
return resp;
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright 1999-2018 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.zuul2.filters.inbound;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Function;
import com.alibaba.csp.sentinel.AsyncEntry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.ResourceTypeConstants;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.HttpRequestMessageItemParser;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.ZuulGatewayApiMatcherManager;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.matcher.HttpRequestMessageApiMatcher;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.SentinelZuul2Constants;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.EntryHolder;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint.SentinelZuulEndpoint;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.netflix.zuul.context.SessionContext;
import com.netflix.zuul.filters.http.HttpInboundFilter;
import com.netflix.zuul.message.http.HttpRequestMessage;
import rx.Observable;
import rx.schedulers.Schedulers;
import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
/**
* The Zuul inbound filter wrapped with Sentinel route and customized API group entries.
*
* @author wavesZh
*/
public class SentinelZuulInboundFilter extends HttpInboundFilter {
private static final String DEFAULT_BLOCK_ENDPOINT_NAME = SentinelZuulEndpoint.class.getCanonicalName();
private final int order;
private final String blockedEndpointName;
/**
* If the executor is null, flow control action will be performed on I/O thread
*/
private final Executor executor;
/**
* If true, the rest of inbound filters will be skipped when the request is blocked.
*/
private final boolean fastError;
private final Function<HttpRequestMessage, String> routeExtractor;
private final GatewayParamParser<HttpRequestMessage> paramParser = new GatewayParamParser<>(
new HttpRequestMessageItemParser());
/**
* Constructor of the inbound filter, which extracts the route from the context route VIP attribute by default.
*
* @param order the order of the filter
*/
public SentinelZuulInboundFilter(int order) {
this(order, m -> m.getContext().getRouteVIP());
}
public SentinelZuulInboundFilter(int order, Function<HttpRequestMessage, String> routeExtractor) {
this(order, null, routeExtractor);
}
public SentinelZuulInboundFilter(int order, Executor executor, Function<HttpRequestMessage, String> routeExtractor) {
this(order, DEFAULT_BLOCK_ENDPOINT_NAME, executor, true, routeExtractor);
}
/**
* Constructor of the inbound filter.
*
* @param order the order of the filter
* @param blockedEndpointName the endpoint to go when the request is blocked
* @param executor the executor where Sentinel do flow checking. If null, it will be executed in current thread.
* @param fastError whether the rest of the filters will be skipped if the request is blocked
* @param routeExtractor the route ID extractor
*/
public SentinelZuulInboundFilter(int order, String blockedEndpointName, Executor executor, boolean fastError,
Function<HttpRequestMessage, String> routeExtractor) {
AssertUtil.notEmpty(blockedEndpointName, "blockedEndpointName cannot be empty");
AssertUtil.notNull(routeExtractor, "routeExtractor cannot be null");
this.order = order;
this.blockedEndpointName = blockedEndpointName;
this.executor = executor;
this.fastError = fastError;
this.routeExtractor = routeExtractor;
}
@Override
public int filterOrder() {
return order;
}
@Override
public Observable<HttpRequestMessage> applyAsync(HttpRequestMessage request) {
if (executor != null) {
return Observable.just(request).subscribeOn(Schedulers.from(executor)).flatMap(this::apply);
} else {
return Observable.just(request).flatMap(this::apply);
}
}
private Observable<HttpRequestMessage> apply(HttpRequestMessage request) {
SessionContext context = request.getContext();
Deque<EntryHolder> holders = new ArrayDeque<>();
String routeId = routeExtractor.apply(request);
String fallBackRoute = routeId;
try {
if (StringUtil.isNotBlank(routeId)) {
ContextUtil.enter(GATEWAY_CONTEXT_ROUTE_PREFIX + routeId);
doSentinelEntry(routeId, RESOURCE_MODE_ROUTE_ID, request, holders);
}
Set<String> matchingApis = pickMatchingApiDefinitions(request);
if (!matchingApis.isEmpty() && ContextUtil.getContext() == null) {
ContextUtil.enter(SentinelZuul2Constants.ZUUL_DEFAULT_CONTEXT);
}
for (String apiName : matchingApis) {
fallBackRoute = apiName;
doSentinelEntry(apiName, RESOURCE_MODE_CUSTOM_API_NAME, request, holders);
}
return Observable.just(request);
} catch (BlockException t) {
context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_BLOCKED_FLAG, Boolean.TRUE);
context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE, fallBackRoute);
if (fastError) {
context.setShouldSendErrorResponse(true);
context.setErrorEndpoint(blockedEndpointName);
} else {
context.setEndpoint(blockedEndpointName);
}
return Observable.error(t);
} finally {
if (!holders.isEmpty()) {
context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY, holders);
}
// clear context to avoid another request use incorrect context
ContextUtil.exit();
}
}
private void doSentinelEntry(String resourceName, final int resType, HttpRequestMessage input, Deque<EntryHolder> holders) throws BlockException {
Object[] params = paramParser.parseParameterFor(resourceName, input, r -> r.getResourceMode() == resType);
AsyncEntry entry = SphU.asyncEntry(resourceName, ResourceTypeConstants.COMMON_API_GATEWAY, EntryType.IN, params);
holders.push(new EntryHolder(entry, params));
}
private Set<String> pickMatchingApiDefinitions(HttpRequestMessage message) {
Set<String> apis = new HashSet<>();
for (HttpRequestMessageApiMatcher matcher : ZuulGatewayApiMatcherManager.getApiMatcherMap().values()) {
if (matcher.test(message)) {
apis.add(matcher.getApiName());
}
}
return apis;
}
@Override
public boolean shouldFilter(HttpRequestMessage msg) {
return true;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 1999-2018 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.zuul2.filters.outbound;
import java.util.Deque;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.SentinelZuul2Constants;
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.EntryHolder;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.netflix.zuul.context.SessionContext;
import com.netflix.zuul.filters.http.HttpOutboundFilter;
import com.netflix.zuul.message.http.HttpResponseMessage;
import rx.Observable;
/**
* The Zuul outbound filter which will complete the Sentinel entries and
* trace the exception that happened in previous filters.
*
* @author wavesZh
*/
public class SentinelZuulOutboundFilter extends HttpOutboundFilter {
private final int order;
public SentinelZuulOutboundFilter(int order) {
this.order = order;
}
@Override
public int filterOrder() {
return order;
}
@Override
public Observable<HttpResponseMessage> applyAsync(HttpResponseMessage input) {
return Observable.just(apply(input));
}
public HttpResponseMessage apply(HttpResponseMessage response) {
SessionContext context = response.getContext();
if (context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY) == null) {
return response;
}
boolean previousBlocked = context.getFilterErrors().stream()
.anyMatch(e -> BlockException.isBlockException(e.getException()));
Deque<EntryHolder> holders = (Deque<EntryHolder>) context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY);
while (!holders.isEmpty()) {
EntryHolder holder = holders.pop();
if (!previousBlocked) {
Tracer.traceEntry(context.getError(), holder.getEntry());
holder.getEntry().exit(1, holder.getParams());
}
}
return response;
}
@Override
public boolean shouldFilter(HttpResponseMessage msg) {
return true;
}
}

View File

@@ -0,0 +1 @@
com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.ZuulApiDefinitionChangeObserver

View File

@@ -0,0 +1,61 @@
/*
* Copyright 1999-2018 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.zuul2.fallback;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import org.junit.Assert;
import org.junit.Test;
/**
* @author tiger
*/
public class ZuulBlockFallbackManagerTest {
private String ROUTE = "/test";
private String DEFAULT_ROUTE = "*";
class MyNullResponseFallBackProvider implements ZuulBlockFallbackProvider {
@Override
public String getRoute() {
return ROUTE;
}
@Override
public BlockResponse fallbackResponse(String route, Throwable cause) {
return null;
}
}
@Test
public void testRegisterProvider() throws Exception {
MyNullResponseFallBackProvider myNullResponseFallBackProvider = new MyNullResponseFallBackProvider();
ZuulBlockFallbackManager.registerProvider(myNullResponseFallBackProvider);
Assert.assertEquals(myNullResponseFallBackProvider.getRoute(), ROUTE);
Assert.assertNull(myNullResponseFallBackProvider.fallbackResponse(ROUTE, new FlowException("flow ex")));
}
@Test
public void clear() {
MyNullResponseFallBackProvider myNullResponseFallBackProvider = new MyNullResponseFallBackProvider();
ZuulBlockFallbackManager.registerProvider(myNullResponseFallBackProvider);
Assert.assertEquals(myNullResponseFallBackProvider.getRoute(), ROUTE);
ZuulBlockFallbackManager.clear();
Assert.assertEquals(ZuulBlockFallbackManager.getFallbackProvider(ROUTE).getRoute(), DEFAULT_ROUTE);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 1999-2018 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.zuul2.fallback;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import org.junit.Assert;
import org.junit.Test;
/**
* @author tiger
*/
public class ZuulBlockFallbackProviderTest {
private String ALL_ROUTE = "*";
@Test
public void testGetNullRoute() throws Exception {
ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(null);
Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE);
}
@Test
public void testGetDefaultRoute() throws Exception {
ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE);
Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE);
}
@Test
public void testGetNotInCacheRoute() throws Exception {
ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider("/not/in");
Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE);
}
@Test
public void testFlowControlFallbackResponse() throws Exception {
ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE);
BlockResponse clientHttpResponse = fallbackProvider.fallbackResponse(ALL_ROUTE,
new FlowException("flow exception"));
Assert.assertEquals(clientHttpResponse.getCode(), 429);
}
@Test
public void testRuntimeExceptionFallbackResponse() throws Exception {
ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE);
BlockResponse clientHttpResponse = fallbackProvider.fallbackResponse(ALL_ROUTE, new RuntimeException());
Assert.assertEquals(clientHttpResponse.getCode(), 500);
}
}