Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -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); | |||||
ZuulBlockFallbackProvider zuulBlockFallbackProvider = ZuulBlockFallbackManager.getFallbackProvider( | |||||
fallBackRoute); | |||||
String fallBackRoute = (String) context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE); | |||||
ZuulBlockFallbackProvider zuulBlockFallbackProvider = ZuulBlockFallbackManager | |||||
.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.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.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; | |||||
* false even if blocked, user can invoke other inbound filters by yourself. | |||||
* If true, the rest of inbound filters will be skipped when the request is blocked. | |||||
*/ | */ | ||||
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) { | |||||
this(order, DEFAULT_BLOCK_ENDPOINT_NAME, executor, true); | |||||
} | |||||
public SentinelZuulInboundFilter(int order, Function<HttpRequestMessage, String> routeExtractor) { | |||||
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(ZuulConstant.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE, fallBackRoute); | |||||
context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_BLOCKED_FLAG, Boolean.TRUE); | |||||
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. | |||||
* <p> | |||||
* The filter will complete the entries and trace the exception that happen in previous filters. | |||||
* The Zuul outbound filter which will complete the Sentinel entries and | |||||
* trace the exception that happened in previous filters. | |||||
* | * | ||||
* @author wavesZh | * @author wavesZh | ||||
*/ | */ | ||||
@@ -61,23 +54,18 @@ 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() | |||||
.filter(e -> BlockException.isBlockException(e.getException())) | |||||
.collect(Collectors.toList()); | |||||
boolean notBlocked = true; | |||||
if (CollectionUtils.isEmpty(errors)) { | |||||
notBlocked = false; | |||||
} | |||||
Deque<EntryHolder> holders = (Deque<EntryHolder>) context.get(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY); | |||||
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()) { | 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(); | |||||
} | |||||
} | |||||
} |