@@ -26,6 +26,7 @@ | |||||
<module>sentinel-api-gateway-adapter-common</module> | <module>sentinel-api-gateway-adapter-common</module> | ||||
<module>sentinel-spring-cloud-gateway-adapter</module> | <module>sentinel-spring-cloud-gateway-adapter</module> | ||||
<module>sentinel-spring-webmvc-adapter</module> | <module>sentinel-spring-webmvc-adapter</module> | ||||
<module>sentinel-zuul2-adapter</module> | |||||
</modules> | </modules> | ||||
<dependencyManagement> | <dependencyManagement> | ||||
@@ -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(); | |||||
} | |||||
} | |||||
``` |
@@ -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> |
@@ -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)); | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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() {} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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() {} | |||||
} |
@@ -0,0 +1,30 @@ | |||||
/* | |||||
* 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 ""; | |||||
} | |||||
} |
@@ -0,0 +1,35 @@ | |||||
/* | |||||
* 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); | |||||
} |
@@ -0,0 +1,38 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* 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() {} | |||||
} |
@@ -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.constants; | |||||
/** | |||||
* @author wavesZh | |||||
*/ | |||||
public class ZuulConstant { | |||||
/** | |||||
* Zuul use Sentinel as default context when serviceId is empty. | |||||
*/ | |||||
public static final String ZUUL_DEFAULT_CONTEXT = "zuul_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 ZuulConstant(){} | |||||
} |
@@ -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 + "\"" + | |||||
'}'; | |||||
} | |||||
} |
@@ -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, "Sentinel block exception", route); | |||||
} else { | |||||
return new BlockResponse(500, "System Error", route); | |||||
} | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
} |
@@ -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); | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -0,0 +1,47 @@ | |||||
/* | |||||
* 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.ZuulConstant; | |||||
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(ZuulConstant.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; | |||||
} | |||||
} |
@@ -0,0 +1,164 @@ | |||||
/* | |||||
* 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 com.alibaba.csp.sentinel.AsyncEntry; | |||||
import com.alibaba.csp.sentinel.EntryType; | |||||
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.callback.ZuulGatewayCallbackManager; | |||||
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.endpoint.SentinelZuulEndpoint; | |||||
import com.alibaba.csp.sentinel.context.ContextUtil; | |||||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||||
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.*; | |||||
/** | |||||
* Zuul2 inboundFilter for Sentinel. | |||||
* | |||||
* @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 executor is null, flow control action will do on I/O thread | |||||
*/ | |||||
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. | |||||
*/ | |||||
private final boolean fastError; | |||||
private final GatewayParamParser<HttpRequestMessage> paramParser = new GatewayParamParser<>( | |||||
new HttpRequestMessageItemParser()); | |||||
public SentinelZuulInboundFilter(int order) { | |||||
this(order, null); | |||||
} | |||||
public SentinelZuulInboundFilter(int order, Executor executor) { | |||||
this(order, DEFAULT_BLOCK_ENDPOINT_NAME, executor, true); | |||||
} | |||||
public SentinelZuulInboundFilter(int order, String blockedEndpointName, Executor executor, boolean fastError) { | |||||
this.order = order; | |||||
this.blockedEndpointName = blockedEndpointName; | |||||
this.executor = executor; | |||||
this.fastError = fastError; | |||||
} | |||||
@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(); | |||||
String origin = parseOrigin(request); | |||||
Deque<EntryHolder> holders = new ArrayDeque<>(); | |||||
String routeId = context.getRouteVIP(); | |||||
String fallBackRoute = routeId; | |||||
try { | |||||
if (StringUtil.isNotBlank(routeId)) { | |||||
ContextUtil.enter(GATEWAY_CONTEXT_ROUTE_PREFIX + routeId, origin); | |||||
doSentinelEntry(routeId, RESOURCE_MODE_ROUTE_ID, request, holders); | |||||
} | |||||
Set<String> matchingApis = pickMatchingApiDefinitions(request); | |||||
if (!matchingApis.isEmpty() && ContextUtil.getContext() == null) { | |||||
ContextUtil.enter(ZuulConstant.ZUUL_DEFAULT_CONTEXT, origin); | |||||
} | |||||
for (String apiName : matchingApis) { | |||||
fallBackRoute = apiName; | |||||
doSentinelEntry(apiName, RESOURCE_MODE_CUSTOM_API_NAME, request, holders); | |||||
} | |||||
return Observable.just(request); | |||||
} catch (BlockException t) { | |||||
context.put(ZuulConstant.ZUUL_CTX_SENTINEL_BLOCKED_FLAG, Boolean.TRUE); | |||||
context.put(ZuulConstant.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(ZuulConstant.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, EntryType.IN, 1, params); | |||||
holders.push(new EntryHolder(entry, params)); | |||||
} | |||||
private String parseOrigin(HttpRequestMessage request) { | |||||
return ZuulGatewayCallbackManager.getOriginParser().parseOrigin(request); | |||||
} | |||||
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; | |||||
} | |||||
} |
@@ -0,0 +1,90 @@ | |||||
/* | |||||
* 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 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.adapter.gateway.zuul2.constants.ZuulConstant; | |||||
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.netflix.zuul.context.SessionContext; | |||||
import com.netflix.zuul.filters.FilterError; | |||||
import com.netflix.zuul.filters.http.HttpOutboundFilter; | |||||
import com.netflix.zuul.message.http.HttpResponseMessage; | |||||
import org.apache.commons.collections.CollectionUtils; | |||||
import rx.Observable; | |||||
/** | |||||
* Zuul2 outboundFilter for Sentinel. | |||||
* <p> | |||||
* The filter will complete the entries and trace the exception that happen 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(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY) == null) { | |||||
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); | |||||
while (!holders.isEmpty()) { | |||||
EntryHolder holder = holders.pop(); | |||||
if (notBlocked) { | |||||
Tracer.traceEntry(context.getError(), holder.getEntry()); | |||||
} | |||||
holder.getEntry().exit(1, holder.getParams()); | |||||
} | |||||
return response; | |||||
} | |||||
@Override | |||||
public boolean shouldFilter(HttpResponseMessage msg) { | |||||
return true; | |||||
} | |||||
} |
@@ -0,0 +1 @@ | |||||
com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.ZuulApiDefinitionChangeObserver |
@@ -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); | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -37,6 +37,7 @@ | |||||
<module>sentinel-demo-zuul-gateway</module> | <module>sentinel-demo-zuul-gateway</module> | ||||
<module>sentinel-demo-etcd-datasource</module> | <module>sentinel-demo-etcd-datasource</module> | ||||
<module>sentinel-demo-spring-webmvc</module> | <module>sentinel-demo-spring-webmvc</module> | ||||
<module>sentinel-demo-zuul2-gateway</module> | |||||
<module>sentinel-demo-log-logback</module> | <module>sentinel-demo-log-logback</module> | ||||
</modules> | </modules> | ||||
@@ -0,0 +1,48 @@ | |||||
<?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-demo</artifactId> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<version>1.7.2-SNAPSHOT</version> | |||||
</parent> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<artifactId>sentinel-demo-zuul2-gateway</artifactId> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-zuul2-adapter</artifactId> | |||||
<version>${project.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-transport-simple-http</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.netflix.zuul</groupId> | |||||
<artifactId>zuul-core</artifactId> | |||||
<version>2.1.5</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.slf4j</groupId> | |||||
<artifactId>slf4j-api</artifactId> | |||||
<version>1.7.28</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.slf4j</groupId> | |||||
<artifactId>slf4j-log4j12</artifactId> | |||||
<version>1.7.28</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.apache.httpcomponents</groupId> | |||||
<artifactId>httpclient</artifactId> | |||||
<version>4.5.8</version> | |||||
</dependency> | |||||
</dependencies> | |||||
</project> |
@@ -0,0 +1,96 @@ | |||||
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,34 @@ | |||||
package com.alibaba.csp.sentinel.demo.zuul2.gateway; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import java.util.Set; | |||||
import javax.annotation.PostConstruct; | |||||
import javax.inject.Inject; | |||||
import com.netflix.zuul.filters.FilterRegistry; | |||||
import com.netflix.zuul.filters.ZuulFilter; | |||||
public class FiltersRegisteringService { | |||||
private final List<ZuulFilter> filters; | |||||
private final FilterRegistry filterRegistry; | |||||
@Inject | |||||
public FiltersRegisteringService(FilterRegistry filterRegistry, Set<ZuulFilter> filters) { | |||||
this.filters = new ArrayList<>(filters); | |||||
this.filterRegistry = filterRegistry; | |||||
} | |||||
public List<ZuulFilter> getFilters() { | |||||
return filters; | |||||
} | |||||
@PostConstruct | |||||
public void initialize() { | |||||
for (ZuulFilter filter: filters) { | |||||
this.filterRegistry.put(filter.filterName(), filter); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,87 @@ | |||||
package com.alibaba.csp.sentinel.demo.zuul2.gateway; | |||||
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.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
import java.util.HashSet; | |||||
import java.util.Set; | |||||
public class GatewayRuleConfig { | |||||
public void doInit() { | |||||
// Prepare some gateway rules and API definitions (only for demo). | |||||
// It's recommended to leverage dynamic data source or the Sentinel dashboard to push the rules. | |||||
initCustomizedApis(); | |||||
initGatewayRules(); | |||||
} | |||||
private void initCustomizedApis() { | |||||
Set<ApiDefinition> definitions = new HashSet<>(); | |||||
ApiDefinition api1 = new ApiDefinition("some_customized_api") | |||||
.setPredicateItems(new HashSet<ApiPredicateItem>() {{ | |||||
add(new ApiPathPredicateItem().setPattern("/images")); | |||||
add(new ApiPathPredicateItem().setPattern("/comments") | |||||
.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); | |||||
} | |||||
private void initGatewayRules() { | |||||
Set<GatewayFlowRule> rules = new HashSet<>(); | |||||
rules.add(new GatewayFlowRule("images") | |||||
.setCount(10) | |||||
.setIntervalSec(1) | |||||
); | |||||
rules.add(new GatewayFlowRule("images") | |||||
.setCount(2) | |||||
.setIntervalSec(2) | |||||
.setBurst(2) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) | |||||
) | |||||
); | |||||
rules.add(new GatewayFlowRule("comments") | |||||
.setCount(3) | |||||
.setIntervalSec(1) | |||||
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) | |||||
.setMaxQueueingTimeoutMs(6000) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) | |||||
.setFieldName("X-Sentinel-Flag") | |||||
) | |||||
); | |||||
rules.add(new GatewayFlowRule("comments") | |||||
.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); | |||||
} | |||||
} |
@@ -0,0 +1,61 @@ | |||||
package com.alibaba.csp.sentinel.demo.zuul2.gateway; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import javax.inject.Inject; | |||||
import javax.inject.Singleton; | |||||
import com.netflix.appinfo.ApplicationInfoManager; | |||||
import com.netflix.config.DynamicIntProperty; | |||||
import com.netflix.discovery.EurekaClient; | |||||
import com.netflix.netty.common.accesslog.AccessLogPublisher; | |||||
import com.netflix.netty.common.channel.config.ChannelConfig; | |||||
import com.netflix.netty.common.channel.config.CommonChannelConfigKeys; | |||||
import com.netflix.netty.common.metrics.EventLoopGroupMetrics; | |||||
import com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler; | |||||
import com.netflix.netty.common.ssl.ServerSslConfig; | |||||
import com.netflix.netty.common.status.ServerStatusManager; | |||||
import com.netflix.spectator.api.Registry; | |||||
import com.netflix.zuul.FilterLoader; | |||||
import com.netflix.zuul.FilterUsageNotifier; | |||||
import com.netflix.zuul.RequestCompleteHandler; | |||||
import com.netflix.zuul.context.SessionContextDecorator; | |||||
import com.netflix.zuul.netty.server.BaseServerStartup; | |||||
import com.netflix.zuul.netty.server.DirectMemoryMonitor; | |||||
import com.netflix.zuul.netty.server.ZuulServerChannelInitializer; | |||||
import io.netty.channel.ChannelInitializer; | |||||
import io.netty.channel.group.ChannelGroup; | |||||
@Singleton | |||||
public class SampleServerStartup extends BaseServerStartup { | |||||
@Inject | |||||
public SampleServerStartup(ServerStatusManager serverStatusManager, FilterLoader filterLoader, SessionContextDecorator sessionCtxDecorator, FilterUsageNotifier usageNotifier, RequestCompleteHandler reqCompleteHandler, Registry registry, DirectMemoryMonitor directMemoryMonitor, EventLoopGroupMetrics eventLoopGroupMetrics, EurekaClient discoveryClient, ApplicationInfoManager applicationInfoManager, AccessLogPublisher accessLogPublisher) { | |||||
super(serverStatusManager, filterLoader, sessionCtxDecorator, usageNotifier, reqCompleteHandler, registry, directMemoryMonitor, eventLoopGroupMetrics, discoveryClient, applicationInfoManager, accessLogPublisher); | |||||
} | |||||
@Override | |||||
protected Map<Integer, ChannelInitializer> choosePortsAndChannels(ChannelGroup clientChannels) { | |||||
Map<Integer, ChannelInitializer> portsToChannels = new HashMap<>(); | |||||
int port = new DynamicIntProperty("zuul.server.port.main", 8085).get(); | |||||
String mainPortName = "main"; | |||||
ChannelConfig channelConfig = BaseServerStartup.defaultChannelConfig(mainPortName); | |||||
ServerSslConfig sslConfig; | |||||
/* These settings may need to be tweaked depending if you're running behind an ELB HTTP listener, TCP listener, | |||||
* or directly on the internet. | |||||
*/ | |||||
ChannelConfig channelDependencies = defaultChannelDependencies(mainPortName); | |||||
channelConfig.set(CommonChannelConfigKeys.allowProxyHeadersWhen, StripUntrustedProxyHeadersHandler.AllowWhen.ALWAYS); | |||||
channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, false); | |||||
channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false); | |||||
channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, false); | |||||
portsToChannels.put(port, new ZuulServerChannelInitializer(port, channelConfig, channelDependencies, clientChannels)); | |||||
logPortConfigured(port, null); | |||||
return portsToChannels; | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
package com.alibaba.csp.sentinel.demo.zuul2.gateway; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint.SentinelZuulEndpoint; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.inbound.SentinelZuulInboundFilter; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.outbound.SentinelZuulOutboundFilter; | |||||
import com.alibaba.csp.sentinel.demo.zuul2.gateway.filters.Route; | |||||
import com.google.inject.AbstractModule; | |||||
import com.google.inject.multibindings.Multibinder; | |||||
import com.netflix.zuul.BasicFilterUsageNotifier; | |||||
import com.netflix.zuul.DynamicCodeCompiler; | |||||
import com.netflix.zuul.FilterFactory; | |||||
import com.netflix.zuul.FilterUsageNotifier; | |||||
import com.netflix.zuul.filters.ZuulFilter; | |||||
import com.netflix.zuul.groovy.GroovyCompiler; | |||||
import com.netflix.zuul.guice.GuiceFilterFactory; | |||||
public class ZuulClasspathFiltersModule extends AbstractModule { | |||||
@Override | |||||
protected void configure() { | |||||
bind(DynamicCodeCompiler.class).to(GroovyCompiler.class); | |||||
bind(FilterFactory.class).to(GuiceFilterFactory.class); | |||||
bind(FilterUsageNotifier.class).to(BasicFilterUsageNotifier.class); | |||||
Multibinder<ZuulFilter> filterMultibinder = Multibinder.newSetBinder(binder(), ZuulFilter.class); | |||||
filterMultibinder.addBinding().toInstance(new SentinelZuulInboundFilter(500)); | |||||
filterMultibinder.addBinding().toInstance(new SentinelZuulOutboundFilter(500)); | |||||
filterMultibinder.addBinding().toInstance(new SentinelZuulEndpoint()); | |||||
filterMultibinder.addBinding().toInstance(new Route()); | |||||
} | |||||
} |
@@ -0,0 +1,61 @@ | |||||
package com.alibaba.csp.sentinel.demo.zuul2.gateway; | |||||
import com.google.inject.AbstractModule; | |||||
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs; | |||||
import com.netflix.discovery.DiscoveryClient; | |||||
import com.netflix.netty.common.accesslog.AccessLogPublisher; | |||||
import com.netflix.netty.common.status.ServerStatusManager; | |||||
import com.netflix.spectator.api.DefaultRegistry; | |||||
import com.netflix.spectator.api.Registry; | |||||
import com.netflix.zuul.BasicRequestCompleteHandler; | |||||
import com.netflix.zuul.FilterFileManager; | |||||
import com.netflix.zuul.RequestCompleteHandler; | |||||
import com.netflix.zuul.context.SessionContextDecorator; | |||||
import com.netflix.zuul.context.ZuulSessionContextDecorator; | |||||
import com.netflix.zuul.init.ZuulFiltersModule; | |||||
import com.netflix.zuul.netty.server.BaseServerStartup; | |||||
import com.netflix.zuul.netty.server.ClientRequestReceiver; | |||||
import com.netflix.zuul.origins.BasicNettyOriginManager; | |||||
import com.netflix.zuul.origins.OriginManager; | |||||
import com.netflix.zuul.stats.BasicRequestMetricsPublisher; | |||||
import com.netflix.zuul.stats.RequestMetricsPublisher; | |||||
/** | |||||
* Zuul Sample Module | |||||
* | |||||
* Author: Arthur Gonigberg | |||||
* Date: November 20, 2017 | |||||
*/ | |||||
public class ZuulSampleModule extends AbstractModule { | |||||
@Override | |||||
protected void configure() { | |||||
// sample specific bindings | |||||
bind(BaseServerStartup.class).to(SampleServerStartup.class); | |||||
// use provided basic netty origin manager | |||||
bind(OriginManager.class).to(BasicNettyOriginManager.class); | |||||
// zuul filter loading | |||||
install(new ZuulFiltersModule()); | |||||
bind(FilterFileManager.class).asEagerSingleton(); | |||||
install(new ZuulClasspathFiltersModule()); | |||||
// general server bindings | |||||
// health/discovery status | |||||
bind(ServerStatusManager.class); | |||||
// decorate new sessions when requests come in | |||||
bind(SessionContextDecorator.class).to(ZuulSessionContextDecorator.class); | |||||
// atlas metrics registry | |||||
bind(Registry.class).to(DefaultRegistry.class); | |||||
// metrics post-request completion | |||||
bind(RequestCompleteHandler.class).to(BasicRequestCompleteHandler.class); | |||||
// discovery client | |||||
bind(AbstractDiscoveryClientOptionalArgs.class).to(DiscoveryClient.DiscoveryClientOptionalArgs.class); | |||||
// timings publisher | |||||
bind(RequestMetricsPublisher.class).to(BasicRequestMetricsPublisher.class); | |||||
// access logger, including request ID generator | |||||
bind(AccessLogPublisher.class).toInstance(new AccessLogPublisher("ACCESS", | |||||
(channel, httpRequest) -> ClientRequestReceiver.getRequestFromChannel(channel).getContext().getUUID())); | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
package com.alibaba.csp.sentinel.demo.zuul2.gateway.filters; | |||||
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; | |||||
import org.apache.http.HttpStatus; | |||||
public class NotFoundEndpoint extends HttpSyncEndpoint { | |||||
@Override | |||||
public HttpResponseMessage apply(HttpRequestMessage request) { | |||||
HttpResponseMessage response = new HttpResponseMessageImpl(request.getContext(), request, HttpStatus.SC_NOT_FOUND); | |||||
response.finishBufferedBodyIfIncomplete(); | |||||
return response; | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
package com.alibaba.csp.sentinel.demo.zuul2.gateway.filters; | |||||
import com.netflix.zuul.context.SessionContext; | |||||
import com.netflix.zuul.filters.http.HttpInboundSyncFilter; | |||||
import com.netflix.zuul.message.http.HttpRequestMessage; | |||||
import com.netflix.zuul.netty.filter.ZuulEndPointRunner; | |||||
public class Route extends HttpInboundSyncFilter { | |||||
@Override | |||||
public HttpRequestMessage apply(HttpRequestMessage request) { | |||||
SessionContext context = request.getContext(); | |||||
switch (request.getPath()) { | |||||
case "/images": | |||||
context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME); | |||||
context.setRouteVIP("images"); | |||||
break; | |||||
case "/comments": | |||||
context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME); | |||||
context.setRouteVIP("comments"); | |||||
break; | |||||
default: | |||||
context.setEndpoint(NotFoundEndpoint.class.getCanonicalName()); | |||||
} | |||||
return request; | |||||
} | |||||
@Override | |||||
public int filterOrder() { | |||||
return 0; | |||||
} | |||||
@Override | |||||
public boolean shouldFilter(HttpRequestMessage msg) { | |||||
return true; | |||||
} | |||||
} |
@@ -0,0 +1,16 @@ | |||||
zuul.server.port.main=8887 | |||||
# Deactivate Eureka | |||||
eureka.registration.enabled = false | |||||
eureka.preferSameZone = false | |||||
eureka.shouldUseDns = false | |||||
eureka.shouldFetchRegistry=false | |||||
# Loading Filters | |||||
zuul.filters.packages = com.netflix.zuul.filters.common,com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint,com.alibaba.csp.sentinel.demo.zuul2.gateway.filters | |||||
# Routing to proxied back-end services | |||||
comments.ribbon.listOfServers=localhost:8081 | |||||
comments.ribbon.client.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList | |||||
images.ribbon.listOfServers=localhost:8082 | |||||
images.ribbon.client.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList |
@@ -0,0 +1,6 @@ | |||||
log4j.rootLogger=INFO,stdout | |||||
# stdout | |||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender | |||||
log4j.appender.stdout.layout=com.netflix.zuul.logging.FilteredPatternLayout | |||||
log4j.appender.stdout.layout.ConversionPattern=%d %-5p %c [%t] %m%n |