Polish code and demo of Sentinel Zuul 2.x adapter
Signed-off-by: Eric Zhao <sczyh16@gmail.com>
This commit is contained in:
parent
5b9865db1c
commit
0536fb6846
|
|
@ -3,11 +3,11 @@
|
||||||
This adapter provides **route level** and **customized API level**
|
This adapter provides **route level** and **customized API level**
|
||||||
flow control for Zuul 2.x API Gateway.
|
flow control for Zuul 2.x API Gateway.
|
||||||
|
|
||||||
> *Note*: this adapter only support Zuul 2.x.
|
> *Note*: this adapter only supports Zuul 2.x.
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
> You can refer to demo `sentinel-demo-zuul2-gateway`
|
> 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`:
|
1. Add Maven dependency to your `pom.xml`:
|
||||||
|
|
||||||
|
|
@ -29,9 +29,9 @@ filterMultibinder.addBinding().toInstance(new SentinelZuulEndpoint());
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
As Zuul 2.x is based on netty, a event-drive model, so we use `AsyncEntry` to do flow control.
|
As Zuul 2.x is based on Netty, an event-driven asynchronous model, so we use `AsyncEntry`.
|
||||||
|
|
||||||
- `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.
|
- `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.
|
- `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.
|
- `SentinelZuulEndpoint`: When an exception is caught, the filter will find a fallback to execute.
|
||||||
|
|
||||||
|
|
@ -40,6 +40,8 @@ As Zuul 2.x is based on netty, a event-drive model, so we use `AsyncEntry` to do
|
||||||
1. Start [Sentinel Dashboard](https://github.com/alibaba/Sentinel/wiki/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.
|
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
|
## Fallbacks
|
||||||
|
|
||||||
You can implement `ZuulBlockFallbackProvider` to define your own fallback provider when Sentinel `BlockException` is thrown.
|
You can implement `ZuulBlockFallbackProvider` to define your own fallback provider when Sentinel `BlockException` is thrown.
|
||||||
|
|
@ -86,16 +88,3 @@ Default block response:
|
||||||
"route":"/"
|
"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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- The Spring library is introduced for AntMatcher -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework</groupId>
|
<groupId>org.springframework</groupId>
|
||||||
<artifactId>spring-core</artifactId>
|
<artifactId>spring-core</artifactId>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
package com.alibaba.csp.sentinel.adapter.gateway.zuul2;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser;
|
import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser;
|
||||||
import com.netflix.zuul.message.http.HttpRequestMessage;
|
import com.netflix.zuul.message.http.HttpRequestMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wavesZh
|
||||||
|
* @since 1.7.2
|
||||||
|
*/
|
||||||
public class HttpRequestMessageItemParser implements RequestItemParser<HttpRequestMessage> {
|
public class HttpRequestMessageItemParser implements RequestItemParser<HttpRequestMessage> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeOb
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Eric Zhao
|
* @author Eric Zhao
|
||||||
* @since 1.6.0
|
* @since 1.7.2
|
||||||
*/
|
*/
|
||||||
public class ZuulApiDefinitionChangeObserver implements ApiDefinitionChangeObserver {
|
public class ZuulApiDefinitionChangeObserver implements ApiDefinitionChangeObserver {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.matcher.HttpRequestMes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author wavesZh
|
* @author wavesZh
|
||||||
|
* @since 1.7.2
|
||||||
*/
|
*/
|
||||||
public final class ZuulGatewayApiMatcherManager {
|
public final class ZuulGatewayApiMatcherManager {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.callback;
|
|
||||||
|
|
||||||
import com.netflix.zuul.message.http.HttpRequestMessage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author wavesZh
|
|
||||||
*/
|
|
||||||
public class DefaultRequestOriginParser implements RequestOriginParser {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String parseOrigin(HttpRequestMessage request) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.callback;
|
|
||||||
|
|
||||||
import com.netflix.zuul.message.http.HttpRequestMessage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The origin parser parses request origin (e.g. IP, user, appName) from HTTP request.
|
|
||||||
*
|
|
||||||
* @author tiger
|
|
||||||
*/
|
|
||||||
public interface RequestOriginParser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the origin from given HTTP request.
|
|
||||||
*
|
|
||||||
* @param request HTTP request
|
|
||||||
* @return parsed origin
|
|
||||||
*/
|
|
||||||
String parseOrigin(HttpRequestMessage request);
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.callback;
|
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eric Zhao
|
|
||||||
* @since 1.6.0
|
|
||||||
*/
|
|
||||||
public final class ZuulGatewayCallbackManager {
|
|
||||||
|
|
||||||
private static volatile RequestOriginParser originParser = new DefaultRequestOriginParser();
|
|
||||||
|
|
||||||
public static RequestOriginParser getOriginParser() {
|
|
||||||
return originParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setOriginParser(RequestOriginParser originParser) {
|
|
||||||
AssertUtil.notNull(originParser, "originParser cannot be null");
|
|
||||||
ZuulGatewayCallbackManager.originParser = originParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ZuulGatewayCallbackManager() {}
|
|
||||||
}
|
|
||||||
|
|
@ -16,15 +16,14 @@
|
||||||
|
|
||||||
package com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants;
|
package com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author wavesZh
|
* @author wavesZh
|
||||||
*/
|
*/
|
||||||
public class ZuulConstant {
|
public class SentinelZuul2Constants {
|
||||||
/**
|
/**
|
||||||
* Zuul use Sentinel as default context when serviceId is empty.
|
* The default entrance (context) name when the routeId is empty.
|
||||||
*/
|
*/
|
||||||
public static final String ZUUL_DEFAULT_CONTEXT = "zuul_default_context";
|
public static final String ZUUL_DEFAULT_CONTEXT = "zuul2_default_context";
|
||||||
/**
|
/**
|
||||||
* Zuul context key for keeping Sentinel entries.
|
* Zuul context key for keeping Sentinel entries.
|
||||||
*/
|
*/
|
||||||
|
|
@ -36,5 +35,5 @@ public class ZuulConstant {
|
||||||
*/
|
*/
|
||||||
public static final String ZUUL_CTX_SENTINEL_BLOCKED_FLAG = "_sentinel_blocked_flag";
|
public static final String ZUUL_CTX_SENTINEL_BLOCKED_FLAG = "_sentinel_blocked_flag";
|
||||||
|
|
||||||
private ZuulConstant(){}
|
private SentinelZuul2Constants() {}
|
||||||
}
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback;
|
||||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Fallback provider for sentinel {@link BlockException}, {@literal *} meant for all routes.
|
* Default fallback provider for Sentinel {@link BlockException}, {@literal *} meant for all routes.
|
||||||
*
|
*
|
||||||
* @author tiger
|
* @author tiger
|
||||||
*/
|
*/
|
||||||
|
|
@ -33,7 +33,7 @@ public class DefaultBlockFallbackProvider implements ZuulBlockFallbackProvider {
|
||||||
@Override
|
@Override
|
||||||
public BlockResponse fallbackResponse(String route, Throwable cause) {
|
public BlockResponse fallbackResponse(String route, Throwable cause) {
|
||||||
if (cause instanceof BlockException) {
|
if (cause instanceof BlockException) {
|
||||||
return new BlockResponse(429, "Sentinel block exception", route);
|
return new BlockResponse(429, "SentinelBlockException", route);
|
||||||
} else {
|
} else {
|
||||||
return new BlockResponse(500, "System Error", route);
|
return new BlockResponse(500, "System Error", route);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,11 @@
|
||||||
|
|
||||||
package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint;
|
package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.ZuulConstant;
|
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.BlockResponse;
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.ZuulBlockFallbackManager;
|
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.ZuulBlockFallbackManager;
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.ZuulBlockFallbackProvider;
|
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.ZuulBlockFallbackProvider;
|
||||||
|
|
||||||
import com.netflix.zuul.context.SessionContext;
|
import com.netflix.zuul.context.SessionContext;
|
||||||
import com.netflix.zuul.filters.http.HttpSyncEndpoint;
|
import com.netflix.zuul.filters.http.HttpSyncEndpoint;
|
||||||
import com.netflix.zuul.message.http.HttpRequestMessage;
|
import com.netflix.zuul.message.http.HttpRequestMessage;
|
||||||
|
|
@ -32,13 +33,14 @@ import com.netflix.zuul.message.http.HttpResponseMessageImpl;
|
||||||
* @author wavesZh
|
* @author wavesZh
|
||||||
*/
|
*/
|
||||||
public class SentinelZuulEndpoint extends HttpSyncEndpoint {
|
public class SentinelZuulEndpoint extends HttpSyncEndpoint {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpResponseMessage apply(HttpRequestMessage request) {
|
public HttpResponseMessage apply(HttpRequestMessage request) {
|
||||||
SessionContext context = request.getContext();
|
SessionContext context = request.getContext();
|
||||||
Throwable throwable = context.getError();
|
Throwable throwable = context.getError();
|
||||||
String fallBackRoute = (String) context.get(ZuulConstant.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE);
|
String fallBackRoute = (String) context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE);
|
||||||
ZuulBlockFallbackProvider zuulBlockFallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(
|
ZuulBlockFallbackProvider zuulBlockFallbackProvider = ZuulBlockFallbackManager
|
||||||
fallBackRoute);
|
.getFallbackProvider(fallBackRoute);
|
||||||
BlockResponse response = zuulBlockFallbackProvider.fallbackResponse(fallBackRoute, throwable);
|
BlockResponse response = zuulBlockFallbackProvider.fallbackResponse(fallBackRoute, throwable);
|
||||||
HttpResponseMessage resp = new HttpResponseMessageImpl(context, request, response.getCode());
|
HttpResponseMessage resp = new HttpResponseMessageImpl(context, request, response.getCode());
|
||||||
resp.setBodyAsText(response.toString());
|
resp.setBodyAsText(response.toString());
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.inbound;
|
package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.inbound;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
|
|
@ -21,20 +20,22 @@ import java.util.Deque;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.AsyncEntry;
|
import com.alibaba.csp.sentinel.AsyncEntry;
|
||||||
import com.alibaba.csp.sentinel.EntryType;
|
import com.alibaba.csp.sentinel.EntryType;
|
||||||
|
import com.alibaba.csp.sentinel.ResourceTypeConstants;
|
||||||
import com.alibaba.csp.sentinel.SphU;
|
import com.alibaba.csp.sentinel.SphU;
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser;
|
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.HttpRequestMessageItemParser;
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.ZuulGatewayApiMatcherManager;
|
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.api.matcher.HttpRequestMessageApiMatcher;
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.callback.ZuulGatewayCallbackManager;
|
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.SentinelZuul2Constants;
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.ZuulConstant;
|
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.EntryHolder;
|
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.adapter.gateway.zuul2.filters.endpoint.SentinelZuulEndpoint;
|
||||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||||
|
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||||
import com.netflix.zuul.context.SessionContext;
|
import com.netflix.zuul.context.SessionContext;
|
||||||
import com.netflix.zuul.filters.http.HttpInboundFilter;
|
import com.netflix.zuul.filters.http.HttpInboundFilter;
|
||||||
|
|
@ -45,7 +46,7 @@ import rx.schedulers.Schedulers;
|
||||||
import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
|
import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zuul2 inboundFilter for Sentinel.
|
* The Zuul inbound filter wrapped with Sentinel route and customized API group entries.
|
||||||
*
|
*
|
||||||
* @author wavesZh
|
* @author wavesZh
|
||||||
*/
|
*/
|
||||||
|
|
@ -57,31 +58,53 @@ public class SentinelZuulInboundFilter extends HttpInboundFilter {
|
||||||
|
|
||||||
private final String blockedEndpointName;
|
private final String blockedEndpointName;
|
||||||
/**
|
/**
|
||||||
* if executor is null, flow control action will do on I/O thread
|
* If the executor is null, flow control action will be performed on I/O thread
|
||||||
*/
|
*/
|
||||||
private final Executor executor;
|
private final Executor executor;
|
||||||
/**
|
/**
|
||||||
* true if blocked but the rest of inbound filters will be skipped;
|
* If true, the rest of inbound filters will be skipped when the request is blocked.
|
||||||
* false even if blocked, user can invoke other inbound filters by yourself.
|
|
||||||
*/
|
*/
|
||||||
private final boolean fastError;
|
private final boolean fastError;
|
||||||
|
private final Function<HttpRequestMessage, String> routeExtractor;
|
||||||
|
|
||||||
private final GatewayParamParser<HttpRequestMessage> paramParser = new GatewayParamParser<>(
|
private final GatewayParamParser<HttpRequestMessage> paramParser = new GatewayParamParser<>(
|
||||||
new HttpRequestMessageItemParser());
|
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) {
|
public SentinelZuulInboundFilter(int order) {
|
||||||
this(order, null);
|
this(order, m -> m.getContext().getRouteVIP());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SentinelZuulInboundFilter(int order, Executor executor) {
|
public SentinelZuulInboundFilter(int order, Function<HttpRequestMessage, String> routeExtractor) {
|
||||||
this(order, DEFAULT_BLOCK_ENDPOINT_NAME, executor, true);
|
this(order, null, routeExtractor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SentinelZuulInboundFilter(int order, String blockedEndpointName, Executor executor, boolean fastError) {
|
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.order = order;
|
||||||
this.blockedEndpointName = blockedEndpointName;
|
this.blockedEndpointName = blockedEndpointName;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.fastError = fastError;
|
this.fastError = fastError;
|
||||||
|
this.routeExtractor = routeExtractor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -100,18 +123,17 @@ public class SentinelZuulInboundFilter extends HttpInboundFilter {
|
||||||
|
|
||||||
private Observable<HttpRequestMessage> apply(HttpRequestMessage request) {
|
private Observable<HttpRequestMessage> apply(HttpRequestMessage request) {
|
||||||
SessionContext context = request.getContext();
|
SessionContext context = request.getContext();
|
||||||
String origin = parseOrigin(request);
|
|
||||||
Deque<EntryHolder> holders = new ArrayDeque<>();
|
Deque<EntryHolder> holders = new ArrayDeque<>();
|
||||||
String routeId = context.getRouteVIP();
|
String routeId = routeExtractor.apply(request);
|
||||||
String fallBackRoute = routeId;
|
String fallBackRoute = routeId;
|
||||||
try {
|
try {
|
||||||
if (StringUtil.isNotBlank(routeId)) {
|
if (StringUtil.isNotBlank(routeId)) {
|
||||||
ContextUtil.enter(GATEWAY_CONTEXT_ROUTE_PREFIX + routeId, origin);
|
ContextUtil.enter(GATEWAY_CONTEXT_ROUTE_PREFIX + routeId);
|
||||||
doSentinelEntry(routeId, RESOURCE_MODE_ROUTE_ID, request, holders);
|
doSentinelEntry(routeId, RESOURCE_MODE_ROUTE_ID, request, holders);
|
||||||
}
|
}
|
||||||
Set<String> matchingApis = pickMatchingApiDefinitions(request);
|
Set<String> matchingApis = pickMatchingApiDefinitions(request);
|
||||||
if (!matchingApis.isEmpty() && ContextUtil.getContext() == null) {
|
if (!matchingApis.isEmpty() && ContextUtil.getContext() == null) {
|
||||||
ContextUtil.enter(ZuulConstant.ZUUL_DEFAULT_CONTEXT, origin);
|
ContextUtil.enter(SentinelZuul2Constants.ZUUL_DEFAULT_CONTEXT);
|
||||||
}
|
}
|
||||||
for (String apiName : matchingApis) {
|
for (String apiName : matchingApis) {
|
||||||
fallBackRoute = apiName;
|
fallBackRoute = apiName;
|
||||||
|
|
@ -119,8 +141,8 @@ public class SentinelZuulInboundFilter extends HttpInboundFilter {
|
||||||
}
|
}
|
||||||
return Observable.just(request);
|
return Observable.just(request);
|
||||||
} catch (BlockException t) {
|
} catch (BlockException t) {
|
||||||
context.put(ZuulConstant.ZUUL_CTX_SENTINEL_BLOCKED_FLAG, Boolean.TRUE);
|
context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_BLOCKED_FLAG, Boolean.TRUE);
|
||||||
context.put(ZuulConstant.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE, fallBackRoute);
|
context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE, fallBackRoute);
|
||||||
if (fastError) {
|
if (fastError) {
|
||||||
context.setShouldSendErrorResponse(true);
|
context.setShouldSendErrorResponse(true);
|
||||||
context.setErrorEndpoint(blockedEndpointName);
|
context.setErrorEndpoint(blockedEndpointName);
|
||||||
|
|
@ -130,7 +152,7 @@ public class SentinelZuulInboundFilter extends HttpInboundFilter {
|
||||||
return Observable.error(t);
|
return Observable.error(t);
|
||||||
} finally {
|
} finally {
|
||||||
if (!holders.isEmpty()) {
|
if (!holders.isEmpty()) {
|
||||||
context.put(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY, holders);
|
context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY, holders);
|
||||||
}
|
}
|
||||||
// clear context to avoid another request use incorrect context
|
// clear context to avoid another request use incorrect context
|
||||||
ContextUtil.exit();
|
ContextUtil.exit();
|
||||||
|
|
@ -139,14 +161,10 @@ public class SentinelZuulInboundFilter extends HttpInboundFilter {
|
||||||
|
|
||||||
private void doSentinelEntry(String resourceName, final int resType, HttpRequestMessage input, Deque<EntryHolder> holders) throws BlockException {
|
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);
|
Object[] params = paramParser.parseParameterFor(resourceName, input, r -> r.getResourceMode() == resType);
|
||||||
AsyncEntry entry = SphU.asyncEntry(resourceName, EntryType.IN, 1, params);
|
AsyncEntry entry = SphU.asyncEntry(resourceName, ResourceTypeConstants.COMMON_API_GATEWAY, EntryType.IN, params);
|
||||||
holders.push(new EntryHolder(entry, params));
|
holders.push(new EntryHolder(entry, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String parseOrigin(HttpRequestMessage request) {
|
|
||||||
return ZuulGatewayCallbackManager.getOriginParser().parseOrigin(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> pickMatchingApiDefinitions(HttpRequestMessage message) {
|
private Set<String> pickMatchingApiDefinitions(HttpRequestMessage message) {
|
||||||
Set<String> apis = new HashSet<>();
|
Set<String> apis = new HashSet<>();
|
||||||
for (HttpRequestMessageApiMatcher matcher : ZuulGatewayApiMatcherManager.getApiMatcherMap().values()) {
|
for (HttpRequestMessageApiMatcher matcher : ZuulGatewayApiMatcherManager.getApiMatcherMap().values()) {
|
||||||
|
|
|
||||||
|
|
@ -17,26 +17,19 @@
|
||||||
package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.outbound;
|
package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.outbound;
|
||||||
|
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.AsyncEntry;
|
|
||||||
import com.alibaba.csp.sentinel.Tracer;
|
import com.alibaba.csp.sentinel.Tracer;
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.ZuulConstant;
|
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.EntryHolder;
|
||||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
|
||||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||||
import com.netflix.zuul.context.SessionContext;
|
import com.netflix.zuul.context.SessionContext;
|
||||||
import com.netflix.zuul.filters.FilterError;
|
|
||||||
import com.netflix.zuul.filters.http.HttpOutboundFilter;
|
import com.netflix.zuul.filters.http.HttpOutboundFilter;
|
||||||
import com.netflix.zuul.message.http.HttpResponseMessage;
|
import com.netflix.zuul.message.http.HttpResponseMessage;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zuul2 outboundFilter for Sentinel.
|
* The Zuul outbound filter which will complete the Sentinel entries and
|
||||||
* <p>
|
* trace the exception that happened in previous filters.
|
||||||
* The filter will complete the entries and trace the exception that happen in previous filters.
|
|
||||||
*
|
*
|
||||||
* @author wavesZh
|
* @author wavesZh
|
||||||
*/
|
*/
|
||||||
|
|
@ -61,24 +54,19 @@ public class SentinelZuulOutboundFilter extends HttpOutboundFilter {
|
||||||
public HttpResponseMessage apply(HttpResponseMessage response) {
|
public HttpResponseMessage apply(HttpResponseMessage response) {
|
||||||
SessionContext context = response.getContext();
|
SessionContext context = response.getContext();
|
||||||
|
|
||||||
if (context.get(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY) == null) {
|
if (context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY) == null) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
List<FilterError> errors = context.getFilterErrors().stream()
|
boolean previousBlocked = context.getFilterErrors().stream()
|
||||||
.filter(e -> BlockException.isBlockException(e.getException()))
|
.anyMatch(e -> BlockException.isBlockException(e.getException()));
|
||||||
.collect(Collectors.toList());
|
Deque<EntryHolder> holders = (Deque<EntryHolder>) context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY);
|
||||||
boolean notBlocked = true;
|
|
||||||
if (CollectionUtils.isEmpty(errors)) {
|
|
||||||
notBlocked = false;
|
|
||||||
}
|
|
||||||
Deque<EntryHolder> holders = (Deque<EntryHolder>) context.get(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY);
|
|
||||||
while (!holders.isEmpty()) {
|
while (!holders.isEmpty()) {
|
||||||
EntryHolder holder = holders.pop();
|
EntryHolder holder = holders.pop();
|
||||||
if (notBlocked) {
|
if (!previousBlocked) {
|
||||||
Tracer.traceEntry(context.getError(), holder.getEntry());
|
Tracer.traceEntry(context.getError(), holder.getEntry());
|
||||||
}
|
|
||||||
holder.getEntry().exit(1, holder.getParams());
|
holder.getEntry().exit(1, holder.getParams());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
package com.alibaba.csp.sentinel.demo.zuul2.gateway;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
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.GatewayFlowRule;
|
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
|
|
||||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
|
|
||||||
import com.google.inject.Injector;
|
|
||||||
import com.google.inject.Scopes;
|
|
||||||
import com.netflix.appinfo.EurekaInstanceConfig;
|
|
||||||
import com.netflix.appinfo.providers.MyDataCenterInstanceConfigProvider;
|
|
||||||
import com.netflix.config.ConfigurationManager;
|
|
||||||
import com.netflix.governator.InjectorBuilder;
|
|
||||||
import com.netflix.zuul.netty.server.BaseServerStartup;
|
|
||||||
import com.netflix.zuul.netty.server.Server;
|
|
||||||
|
|
||||||
public class Bootstrap {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
new Bootstrap().start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
Server server;
|
|
||||||
try {
|
|
||||||
new GatewayRuleConfig().doInit();
|
|
||||||
|
|
||||||
ConfigurationManager.loadCascadedPropertiesFromResources("application");
|
|
||||||
Injector injector = InjectorBuilder.fromModule(new ZuulModule()).createInjector();
|
|
||||||
injector.getInstance(FiltersRegisteringService.class);
|
|
||||||
BaseServerStartup serverStartup = injector.getInstance(BaseServerStartup.class);
|
|
||||||
server = serverStartup.server();
|
|
||||||
server.start(true);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initGatewayRules() {
|
|
||||||
Set<GatewayFlowRule> rules = new HashSet<>();
|
|
||||||
rules.add(new GatewayFlowRule("another_customized_api")
|
|
||||||
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
|
|
||||||
.setCount(1)
|
|
||||||
.setIntervalSec(1)
|
|
||||||
.setParamItem(new GatewayParamFlowItem()
|
|
||||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
|
|
||||||
.setFieldName("pa")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
rules.add(new GatewayFlowRule("some_customized_api")
|
|
||||||
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
|
|
||||||
.setCount(5)
|
|
||||||
.setIntervalSec(1)
|
|
||||||
.setParamItem(new GatewayParamFlowItem()
|
|
||||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
|
|
||||||
.setFieldName("pn")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
GatewayRuleManager.loadRules(rules);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initCustomizedApis() {
|
|
||||||
Set<ApiDefinition> definitions = new HashSet<>();
|
|
||||||
ApiDefinition api1 = new ApiDefinition("some_customized_api")
|
|
||||||
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
|
|
||||||
add(new ApiPathPredicateItem().setPattern("/ahas"));
|
|
||||||
add(new ApiPathPredicateItem().setPattern("/aliyun/**")
|
|
||||||
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
|
|
||||||
}});
|
|
||||||
ApiDefinition api2 = new ApiDefinition("another_customized_api")
|
|
||||||
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
|
|
||||||
add(new ApiPathPredicateItem().setPattern("/**")
|
|
||||||
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
|
|
||||||
}});
|
|
||||||
definitions.add(api1);
|
|
||||||
definitions.add(api2);
|
|
||||||
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ZuulModule extends ZuulSampleModule {
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
//DataCenterInfo
|
|
||||||
bind(EurekaInstanceConfig.class)
|
|
||||||
.toProvider(MyDataCenterInstanceConfigProvider.class)
|
|
||||||
.in(Scopes.SINGLETON);
|
|
||||||
super.configure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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.demo.zuul2.gateway;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.Scopes;
|
||||||
|
import com.netflix.appinfo.EurekaInstanceConfig;
|
||||||
|
import com.netflix.appinfo.providers.MyDataCenterInstanceConfigProvider;
|
||||||
|
import com.netflix.config.ConfigurationManager;
|
||||||
|
import com.netflix.governator.InjectorBuilder;
|
||||||
|
import com.netflix.zuul.netty.server.BaseServerStartup;
|
||||||
|
import com.netflix.zuul.netty.server.Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>The Zuul 2.x demo with Sentinel gateway flow control.</p>
|
||||||
|
* <p>Run with {@code -Dcsp.sentinel.api.type=1} to mark the demo as API gateway.</p>
|
||||||
|
*
|
||||||
|
* @author wavesZh
|
||||||
|
*/
|
||||||
|
public class ZuulBootstrap {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
new ZuulBootstrap().start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
Server server;
|
||||||
|
try {
|
||||||
|
// Load sample rules. You may also manage rules in Sentinel dashboard.
|
||||||
|
new GatewayRuleConfig().doInit();
|
||||||
|
|
||||||
|
ConfigurationManager.loadCascadedPropertiesFromResources("application");
|
||||||
|
Injector injector = InjectorBuilder.fromModule(new ZuulModule()).createInjector();
|
||||||
|
injector.getInstance(FiltersRegisteringService.class);
|
||||||
|
BaseServerStartup serverStartup = injector.getInstance(BaseServerStartup.class);
|
||||||
|
server = serverStartup.server();
|
||||||
|
server.start(true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ZuulModule extends ZuulSampleModule {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
//DataCenterInfo
|
||||||
|
bind(EurekaInstanceConfig.class)
|
||||||
|
.toProvider(MyDataCenterInstanceConfigProvider.class)
|
||||||
|
.in(Scopes.SINGLETON);
|
||||||
|
super.configure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue