diff --git a/sentinel-adapter/pom.xml b/sentinel-adapter/pom.xml index d1bdc9e6..bf27a140 100755 --- a/sentinel-adapter/pom.xml +++ b/sentinel-adapter/pom.xml @@ -26,6 +26,7 @@ sentinel-api-gateway-adapter-common sentinel-spring-cloud-gateway-adapter sentinel-spring-webmvc-adapter + sentinel-zuul2-adapter diff --git a/sentinel-adapter/sentinel-zuul2-adapter/README.md b/sentinel-adapter/sentinel-zuul2-adapter/README.md new file mode 100755 index 00000000..943fcaf0 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/README.md @@ -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 + + com.alibaba.csp + sentinel-zuul2-adapter + x.y.z + +``` + +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(); + } +} +``` \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/pom.xml b/sentinel-adapter/sentinel-zuul2-adapter/pom.xml new file mode 100644 index 00000000..132f4b38 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/pom.xml @@ -0,0 +1,62 @@ + + + + sentinel-adapter + com.alibaba.csp + 1.7.2-SNAPSHOT + + 4.0.0 + + sentinel-zuul2-adapter + jar + + + 1.8 + 1.8 + 2.1.5 + + + + + com.alibaba.csp + sentinel-core + + + com.alibaba.csp + sentinel-api-gateway-adapter-common + + + + com.netflix.zuul + zuul-core + ${zuul.version} + provided + + + org.mockito + mockito-core + + + + + + org.springframework + spring-core + 5.1.9.RELEASE + + + + junit + junit + test + + + org.mockito + mockito-core + test + + + + \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/HttpRequestMessageItemParser.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/HttpRequestMessageItemParser.java new file mode 100644 index 00000000..990cfb95 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/HttpRequestMessageItemParser.java @@ -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 { + + @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)); + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulApiDefinitionChangeObserver.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulApiDefinitionChangeObserver.java new file mode 100644 index 00000000..7689647a --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulApiDefinitionChangeObserver.java @@ -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 apiDefinitions) { + ZuulGatewayApiMatcherManager.loadApiDefinitions(apiDefinitions); + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulGatewayApiMatcherManager.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulGatewayApiMatcherManager.java new file mode 100644 index 00000000..3afc74be --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulGatewayApiMatcherManager.java @@ -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 API_MATCHER_MAP = new ConcurrentHashMap<>(); + + public static Map 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 getApiDefinitionSet() { + Set set = new HashSet<>(); + for (HttpRequestMessageApiMatcher matcher : API_MATCHER_MAP.values()) { + set.add(matcher.getApiDefinition()); + } + return set; + } + + static synchronized void loadApiDefinitions(/*@Valid*/ Set 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() {} +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/matcher/HttpRequestMessageApiMatcher.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/matcher/HttpRequestMessageApiMatcher.java new file mode 100644 index 00000000..12924da7 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/matcher/HttpRequestMessageApiMatcher.java @@ -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 { + + public HttpRequestMessageApiMatcher(ApiDefinition apiDefinition) { + super(apiDefinition); + } + + @Override + protected void initializeMatchers() { + if (apiDefinition.getPredicateItems() != null) { + for (ApiPredicateItem item : apiDefinition.getPredicateItems()) { + Predicate predicate = fromApiPredicate(item); + if (predicate != null) { + matchers.add(predicate); + } + } + } + } + + private Predicate fromApiPredicate(/*@NonNull*/ ApiPredicateItem item) { + if (item instanceof ApiPathPredicateItem) { + return fromApiPathPredicate((ApiPathPredicateItem)item); + } + return null; + } + + private Predicate 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); + } + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/PrefixRoutePathMatcher.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/PrefixRoutePathMatcher.java new file mode 100644 index 00000000..2b979bc8 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/PrefixRoutePathMatcher.java @@ -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 { + + 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; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/RegexRoutePathMatcher.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/RegexRoutePathMatcher.java new file mode 100644 index 00000000..fd88e648 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/RegexRoutePathMatcher.java @@ -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 { + + 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; + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/ZuulRouteMatchers.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/ZuulRouteMatchers.java new file mode 100644 index 00000000..7d62c63e --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/ZuulRouteMatchers.java @@ -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 all() { + return requestContext -> true; + } + + public static Predicate antPath(String pathPattern) { + return new PrefixRoutePathMatcher(pathPattern); + } + + public static Predicate exactPath(final String path) { + return exchange -> exchange.getPath().equals(path); + } + + public static Predicate regexPath(String pathPattern) { + return new RegexRoutePathMatcher(pathPattern); + } + + private ZuulRouteMatchers() {} +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/callback/DefaultRequestOriginParser.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/callback/DefaultRequestOriginParser.java new file mode 100644 index 00000000..ed2271df --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/callback/DefaultRequestOriginParser.java @@ -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 ""; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/callback/RequestOriginParser.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/callback/RequestOriginParser.java new file mode 100644 index 00000000..2794b288 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/callback/RequestOriginParser.java @@ -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); +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/callback/ZuulGatewayCallbackManager.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/callback/ZuulGatewayCallbackManager.java new file mode 100644 index 00000000..c70abed4 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/callback/ZuulGatewayCallbackManager.java @@ -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() {} +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/constants/ZuulConstant.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/constants/ZuulConstant.java new file mode 100644 index 00000000..a3902efd --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/constants/ZuulConstant.java @@ -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(){} +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/BlockResponse.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/BlockResponse.java new file mode 100644 index 00000000..5209f232 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/BlockResponse.java @@ -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 + "\"" + + '}'; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/DefaultBlockFallbackProvider.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/DefaultBlockFallbackProvider.java new file mode 100644 index 00000000..031e841a --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/DefaultBlockFallbackProvider.java @@ -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); + } + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManager.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManager.java new file mode 100644 index 00000000..4e94e03a --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManager.java @@ -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 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(); + } + +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProvider.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProvider.java new file mode 100644 index 00000000..ee6f51f3 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProvider.java @@ -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 null + * @return the fallback response + */ + BlockResponse fallbackResponse(String route, Throwable cause); +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/EntryHolder.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/EntryHolder.java new file mode 100644 index 00000000..59fb2ef9 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/EntryHolder.java @@ -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; + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/endpoint/SentinelZuulEndpoint.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/endpoint/SentinelZuulEndpoint.java new file mode 100644 index 00000000..167dd296 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/endpoint/SentinelZuulEndpoint.java @@ -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; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/inbound/SentinelZuulInboundFilter.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/inbound/SentinelZuulInboundFilter.java new file mode 100644 index 00000000..5d2601e8 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/inbound/SentinelZuulInboundFilter.java @@ -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 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 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 apply(HttpRequestMessage request) { + SessionContext context = request.getContext(); + String origin = parseOrigin(request); + Deque 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 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 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 pickMatchingApiDefinitions(HttpRequestMessage message) { + Set 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; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/outbound/SentinelZuulOutboundFilter.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/outbound/SentinelZuulOutboundFilter.java new file mode 100644 index 00000000..75f6a179 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/outbound/SentinelZuulOutboundFilter.java @@ -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. + *

+ * 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 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 errors = context.getFilterErrors().stream() + .filter(e -> BlockException.isBlockException(e.getException())) + .collect(Collectors.toList()); + boolean notBlocked = true; + if (CollectionUtils.isEmpty(errors)) { + notBlocked = false; + } + Deque holders = (Deque) 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; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver b/sentinel-adapter/sentinel-zuul2-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver new file mode 100644 index 00000000..6c27b158 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.ZuulApiDefinitionChangeObserver \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManagerTest.java b/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManagerTest.java new file mode 100644 index 00000000..1b0416d5 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManagerTest.java @@ -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); + } + +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProviderTest.java b/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProviderTest.java new file mode 100644 index 00000000..59f6d74a --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProviderTest.java @@ -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); + } +} \ No newline at end of file diff --git a/sentinel-demo/pom.xml b/sentinel-demo/pom.xml index 2e03da0e..dfb5980f 100755 --- a/sentinel-demo/pom.xml +++ b/sentinel-demo/pom.xml @@ -37,6 +37,7 @@ sentinel-demo-zuul-gateway sentinel-demo-etcd-datasource sentinel-demo-spring-webmvc + sentinel-demo-zuul2-gateway sentinel-demo-log-logback diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/pom.xml b/sentinel-demo/sentinel-demo-zuul2-gateway/pom.xml new file mode 100644 index 00000000..01e562a7 --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/pom.xml @@ -0,0 +1,48 @@ + + + + sentinel-demo + com.alibaba.csp + 1.7.2-SNAPSHOT + + 4.0.0 + + sentinel-demo-zuul2-gateway + + + + com.alibaba.csp + sentinel-zuul2-adapter + ${project.version} + + + com.alibaba.csp + sentinel-transport-simple-http + + + com.netflix.zuul + zuul-core + 2.1.5 + + + + org.slf4j + slf4j-api + 1.7.28 + + + org.slf4j + slf4j-log4j12 + 1.7.28 + + + + org.apache.httpcomponents + httpclient + 4.5.8 + + + + \ No newline at end of file diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/Bootstrap.java b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/Bootstrap.java new file mode 100644 index 00000000..335c3505 --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/Bootstrap.java @@ -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 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 definitions = new HashSet<>(); + ApiDefinition api1 = new ApiDefinition("some_customized_api") + .setPredicateItems(new HashSet() {{ + 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() {{ + 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(); + } + } +} diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/FiltersRegisteringService.java b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/FiltersRegisteringService.java new file mode 100755 index 00000000..c4d1b3c7 --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/FiltersRegisteringService.java @@ -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 filters; + private final FilterRegistry filterRegistry; + + @Inject + public FiltersRegisteringService(FilterRegistry filterRegistry, Set filters) { + this.filters = new ArrayList<>(filters); + this.filterRegistry = filterRegistry; + } + + public List getFilters() { + return filters; + } + + @PostConstruct + public void initialize() { + for (ZuulFilter filter: filters) { + this.filterRegistry.put(filter.filterName(), filter); + } + } +} diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/GatewayRuleConfig.java b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/GatewayRuleConfig.java new file mode 100644 index 00000000..33bea390 --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/GatewayRuleConfig.java @@ -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 definitions = new HashSet<>(); + ApiDefinition api1 = new ApiDefinition("some_customized_api") + .setPredicateItems(new HashSet() {{ + 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() {{ + add(new ApiPathPredicateItem().setPattern("/**") + .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); + }}); + definitions.add(api1); + definitions.add(api2); + GatewayApiDefinitionManager.loadApiDefinitions(definitions); + } + + private void initGatewayRules() { + Set 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); + } +} \ No newline at end of file diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/SampleServerStartup.java b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/SampleServerStartup.java new file mode 100644 index 00000000..cbf86450 --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/SampleServerStartup.java @@ -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 choosePortsAndChannels(ChannelGroup clientChannels) { + Map 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; + } +} diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/ZuulClasspathFiltersModule.java b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/ZuulClasspathFiltersModule.java new file mode 100755 index 00000000..d3d9f505 --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/ZuulClasspathFiltersModule.java @@ -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 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()); + } +} diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/ZuulSampleModule.java b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/ZuulSampleModule.java new file mode 100644 index 00000000..a52d62e9 --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/ZuulSampleModule.java @@ -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())); + } +} diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/filters/NotFoundEndpoint.java b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/filters/NotFoundEndpoint.java new file mode 100644 index 00000000..c50b9746 --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/filters/NotFoundEndpoint.java @@ -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; + } +} \ No newline at end of file diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/filters/Route.java b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/filters/Route.java new file mode 100644 index 00000000..6b0233b2 --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/filters/Route.java @@ -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; + } +} diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/resources/application.properties b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/resources/application.properties new file mode 100644 index 00000000..7c04c2ff --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/resources/application.properties @@ -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 \ No newline at end of file diff --git a/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/resources/log4j.properties b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/resources/log4j.properties new file mode 100644 index 00000000..15b98e6b --- /dev/null +++ b/sentinel-demo/sentinel-demo-zuul2-gateway/src/main/resources/log4j.properties @@ -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 \ No newline at end of file