Skip to content

Commit

Permalink
Add gateway adapter for Zuul 2.x (#1138)
Browse files Browse the repository at this point in the history
- also add demo for Zuul 2.x adapter
  • Loading branch information
wavesZh authored and sczyh30 committed Mar 13, 2020
1 parent 05e3caf commit 5b9865d
Show file tree
Hide file tree
Showing 37 changed files with 1,827 additions and 0 deletions.
1 change: 1 addition & 0 deletions sentinel-adapter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<module>sentinel-api-gateway-adapter-common</module>
<module>sentinel-spring-cloud-gateway-adapter</module>
<module>sentinel-spring-webmvc-adapter</module>
<module>sentinel-zuul2-adapter</module>
</modules>

<dependencyManagement>
Expand Down
101 changes: 101 additions & 0 deletions sentinel-adapter/sentinel-zuul2-adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# 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 support Zuul 2.x.
## How to use

> You can refer to 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, a event-drive model, so we use `AsyncEntry` to do flow control.

- `SentinelZuulInboundFilter`: This inbound filter will regard all proxy ID (`proxy` in `SessionContext`) 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.

## 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":"/"
}
```

## Request origin parser

You can register customized request origin parser like this:

```java
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpRequestMessage request) {
return request.getInboundRequest().getOriginalHost() + ":" + request.getInboundRequest().getOriginalPort();
}
}
```
62 changes: 62 additions & 0 deletions sentinel-adapter/sentinel-zuul2-adapter/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?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.7.2-SNAPSHOT</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>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.9.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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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;

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));
}
}
Original file line number Diff line number Diff line change
@@ -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.6.0
*/
public class ZuulApiDefinitionChangeObserver implements ApiDefinitionChangeObserver {

@Override
public void onChange(Set<ApiDefinition> apiDefinitions) {
ZuulGatewayApiMatcherManager.loadApiDefinitions(apiDefinitions);
}
}
Original file line number Diff line number Diff line change
@@ -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.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
*/
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() {}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading

0 comments on commit 5b9865d

Please sign in to comment.