From 3a1eb563381c777b13e713411ad21d188f1b5f71 Mon Sep 17 00:00:00 2001 From: moon tiger Date: Sat, 12 Jan 2019 10:16:12 +0800 Subject: [PATCH] Add adapter support for Zuul 1.x (#188) - implement `SentinelPreFilter`, `SentinelPostFilter` and `SentinelErrorFilter` - support fallback --- sentinel-adapter/pom.xml | 7 +- .../sentinel-zuul-adapter/README.md | 148 +++++++++++++++++ .../sentinel-zuul-adapter/pom.xml | 48 ++++++ .../adapter/zuul/constants/ZuulConstant.java | 62 +++++++ .../adapter/zuul/fallback/BlockResponse.java | 67 ++++++++ .../DefaultBlockFallbackProvider.java | 43 +++++ .../fallback/DefaultRequestOriginParser.java | 13 ++ .../zuul/fallback/DefaultUrlCleaner.java | 27 +++ .../zuul/fallback/RequestOriginParser.java | 34 ++++ .../adapter/zuul/fallback/UrlCleaner.java | 31 ++++ .../fallback/ZuulBlockFallbackManager.java | 57 +++++++ .../fallback/ZuulBlockFallbackProvider.java | 40 +++++ .../zuul/filters/AbstractSentinelFilter.java | 46 +++++ .../zuul/filters/SentinelErrorFilter.java | 81 +++++++++ .../zuul/filters/SentinelPostFilter.java | 60 +++++++ .../zuul/filters/SentinelPreFilter.java | 112 +++++++++++++ .../properties/SentinelZuulProperties.java | 80 +++++++++ .../adapter/zuul/util/FilterUtil.java | 157 ++++++++++++++++++ .../ZuulBlockFallbackManagerTest.java | 62 +++++++ .../ZuulBlockFallbackProviderTest.java | 61 +++++++ .../zuul/filters/SentinelErrorFilterTest.java | 56 +++++++ .../zuul/filters/SentinelPostFilterTest.java | 45 +++++ .../zuul/filters/SentinelPreFilterTest.java | 106 ++++++++++++ 23 files changed, 1442 insertions(+), 1 deletion(-) create mode 100755 sentinel-adapter/sentinel-zuul-adapter/README.md create mode 100755 sentinel-adapter/sentinel-zuul-adapter/pom.xml create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/constants/ZuulConstant.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/BlockResponse.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultBlockFallbackProvider.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultRequestOriginParser.java create mode 100755 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultUrlCleaner.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/RequestOriginParser.java create mode 100755 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/UrlCleaner.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackManager.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackProvider.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/AbstractSentinelFilter.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelErrorFilter.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPostFilter.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPreFilter.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/properties/SentinelZuulProperties.java create mode 100755 sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/util/FilterUtil.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackManagerTest.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackProviderTest.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelErrorFilterTest.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPostFilterTest.java create mode 100644 sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPreFilterTest.java diff --git a/sentinel-adapter/pom.xml b/sentinel-adapter/pom.xml index cfb8d577..04a8a4ea 100755 --- a/sentinel-adapter/pom.xml +++ b/sentinel-adapter/pom.xml @@ -18,6 +18,7 @@ sentinel-web-servlet sentinel-dubbo-adapter sentinel-grpc-adapter + sentinel-zuul-adapter @@ -32,7 +33,11 @@ sentinel-extension ${project.version} - + + com.alibaba.csp + sentinel-web-servlet + ${project.version} + junit junit diff --git a/sentinel-adapter/sentinel-zuul-adapter/README.md b/sentinel-adapter/sentinel-zuul-adapter/README.md new file mode 100755 index 00000000..dde1c4eb --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/README.md @@ -0,0 +1,148 @@ +# Sentinel Zuul Adapter + +Zuul does not provide rateLimit function, If use default `SentinelRibbonFilter` route filter. it wrapped by Hystrix Command. so only provide Service level +circuit protect. + +Sentinel can provide `ServiceId` level and `API Path` level flow control for zuul gateway service. + +*Note*: this project is for zuul 1. + +## How to use + +1. Add maven dependency + +```xml + + com.alibaba.csp + sentinel-zuul-adapter + x.y.z + + +``` + +2. Register filters + +```java +// get registry +final FilterRegistry r = FilterRegistry.instance(); +// this is property config. set filter ennable +SentinelZuulProperties properties = new SentinelZuulProperties(); +properties.setEnabled(true); +// set url cleaner, here use default +DefaultUrlCleaner defaultUrlCleaner = new DefaultUrlCleaner(); +// set origin parser. here use default +DefaultRequestOriginParser defaultRequestOriginParser = new DefaultRequestOriginParser(); + +// register filters. you must register all three filters. +SentinelPreFilter sentinelPreFilter = new SentinelPreFilter(properties, defaultUrlCleaner, defaultRequestOriginParser); +r.put("sentinelPreFilter", sentinelPreFilter); +SentinelPostFilter postFilter = new SentinelPostFilter(properties); +r.put("sentinelPostFilter", postFilter); +SentinelErrorFilter errorFilter = new SentinelErrorFilter(properties); +r.put("sentinelErrorFilter", errorFilter); +``` + +## How it works + +As Zuul run as per thread per connection block model, we add filters around `route Filter` to trace sentinel statistics. + +- `SentinelPreFilter`: Get an entry of resource,the first order is **ServiceId**(the key in RequestContext is `serviceId`, this can set in own custom filter), then **API Path**. +- `SentinelPostFilter`: When success response,exit entry. +- `SentinelPreFilter`: When get an `Exception`, trace the exception and exit context. + + +the order of Filter can be changed in property: + + + +Filters create structure like: + + +```bash + +EntranceNode: machine-root(t:3 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +-EntranceNode: coke(t:2 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +--coke(t:2 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +---/coke/coke(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +-EntranceNode: sentinel_default_context(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +-EntranceNode: book(t:1 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +--book(t:1 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +---/book/coke(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) + +``` + +`book` and `coke` are serviceId. + +`---/book/coke` is api path, the real uri is `/coke`. + + +## Integration with Sentinel DashBord + +1. Start [Sentinel DashBord](https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0). + +2. Sentinel has full rule config features. see [Dynamic-Rule-Configuration](https://github.com/alibaba/Sentinel/wiki/Dynamic-Rule-Configuration) + +## Fallbacks + +Implements `SentinelFallbackProvider` to define your own Fallback Provider when Sentinel Block Exception throwing. the default +Fallback Provider is `DefaultBlockFallbackProvider`. + +By default Fallback route is `ServiveId + URI PATH`, example `/book/coke`, first `book` is serviceId, `/coke` is URI PATH. so that both +can be needed. + +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 "/coke/coke"; + } + + @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":"/" +} +``` + +## Origin parser + +自定义解析URL + +```java + +public class DefaultRequestOriginParser implements RequestOriginParser { + @Override + public String parseOrigin(HttpServletRequest request) { + return ""; + } +} + +``` diff --git a/sentinel-adapter/sentinel-zuul-adapter/pom.xml b/sentinel-adapter/sentinel-zuul-adapter/pom.xml new file mode 100755 index 00000000..bf2c2373 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/pom.xml @@ -0,0 +1,48 @@ + + + + sentinel-adapter + com.alibaba.csp + 1.4.2-SNAPSHOT + + 4.0.0 + sentinel-zuul-adapter + jar + + + 1.3.1 + 3.1.0 + + + + + com.alibaba.csp + sentinel-core + + + com.netflix.zuul + zuul-core + ${zuul.version} + provided + + + junit + junit + test + + + org.mockito + mockito-core + test + + + javax.servlet + javax.servlet-api + ${servlet.api.version} + provided + + + + \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/constants/ZuulConstant.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/constants/ZuulConstant.java new file mode 100644 index 00000000..244fbf4a --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/constants/ZuulConstant.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.zuul.constants; + +import com.netflix.zuul.ZuulFilter; + +/** + * @author tiger + */ +public class ZuulConstant { + + /** + * Zuul {@link com.netflix.zuul.context.RequestContext} key for use in load balancer. + */ + public static final String SERVICE_ID_KEY = "serviceId"; + + /** + * {@link ZuulFilter#filterType()} error type. + */ + public static final String ERROR_TYPE = "error"; + + /** + * {@link ZuulFilter#filterType()} post type. + */ + public static final String POST_TYPE = "post"; + + /** + * {@link ZuulFilter#filterType()} pre type. + */ + public static final String PRE_TYPE = "pre"; + + /** + * {@link ZuulFilter#filterType()} route type. + */ + public static final String ROUTE_TYPE = "route"; + + /** + * Filter Order for SEND_RESPONSE_FILTER_ORDER + */ + public static final int SEND_RESPONSE_FILTER_ORDER = 1000; + + /** + * Zuul use Sentinel as default context when serviceId is empty. + */ + public static final String ZUUL_DEFAULT_CONTEXT = "zuul_default_context"; + + private ZuulConstant(){} +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/BlockResponse.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/BlockResponse.java new file mode 100644 index 00000000..6fd617fd --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/BlockResponse.java @@ -0,0 +1,67 @@ +/* + * 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.zuul.fallback; + +/** + * Fall back response for {@link com.alibaba.csp.sentinel.slots.block.BlockException} + * + * @author tiger + */ +public class BlockResponse { + 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-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultBlockFallbackProvider.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultBlockFallbackProvider.java new file mode 100644 index 00000000..432db872 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultBlockFallbackProvider.java @@ -0,0 +1,43 @@ +/* + * 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.zuul.fallback; + +import com.alibaba.csp.sentinel.log.RecordLog; +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) { + 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); + } + } +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultRequestOriginParser.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultRequestOriginParser.java new file mode 100644 index 00000000..45dba4d7 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultRequestOriginParser.java @@ -0,0 +1,13 @@ +package com.alibaba.csp.sentinel.adapter.zuul.fallback; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author tiger + */ +public class DefaultRequestOriginParser implements RequestOriginParser { + @Override + public String parseOrigin(HttpServletRequest request) { + return ""; + } +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultUrlCleaner.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultUrlCleaner.java new file mode 100755 index 00000000..09780820 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/DefaultUrlCleaner.java @@ -0,0 +1,27 @@ +/* + * 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.zuul.fallback; + +/*** + * @author tiger + */ +public class DefaultUrlCleaner implements UrlCleaner { + + @Override + public String clean(String originUrl) { + return originUrl; + } +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/RequestOriginParser.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/RequestOriginParser.java new file mode 100644 index 00000000..20f6d744 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/RequestOriginParser.java @@ -0,0 +1,34 @@ +/* + * 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.zuul.fallback; + +import javax.servlet.http.HttpServletRequest; + +/** + * 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(HttpServletRequest request); +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/UrlCleaner.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/UrlCleaner.java new file mode 100755 index 00000000..d6b9a33b --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/UrlCleaner.java @@ -0,0 +1,31 @@ +/* + * 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.zuul.fallback; + +/*** + * @author tiger + */ +public interface UrlCleaner { + + /*** + *

Process the url. Some path variables should be handled and unified.

+ *

e.g. collect_item_relation--10200012121-.html will be converted to collect_item_relation.html

+ * + * @param originUrl original url + * @return processed url + */ + String clean(String originUrl); +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackManager.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackManager.java new file mode 100644 index 00000000..2760acff --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackManager.java @@ -0,0 +1,57 @@ +/* + * 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.zuul.fallback; + +import java.util.HashMap; +import java.util.Map; + +/** + * 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) { + 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-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackProvider.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackProvider.java new file mode 100644 index 00000000..e6b3acb0 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/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.zuul.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-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/AbstractSentinelFilter.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/AbstractSentinelFilter.java new file mode 100644 index 00000000..0becdf53 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/AbstractSentinelFilter.java @@ -0,0 +1,46 @@ +/* + * 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.zuul.filters; + +import com.alibaba.csp.sentinel.adapter.zuul.properties.SentinelZuulProperties; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.netflix.zuul.ZuulFilter; + +/** + * Abstract class for sentinel filters. + * + * @author tiger + */ +public abstract class AbstractSentinelFilter extends ZuulFilter { + + private final SentinelZuulProperties sentinelZuulProperties; + + public SentinelZuulProperties getSentinelZuulProperties() { + return sentinelZuulProperties; + } + + public AbstractSentinelFilter(SentinelZuulProperties sentinelZuulProperties) { + AssertUtil.notNull(sentinelZuulProperties,"SentinelZuulProperties can not be null"); + this.sentinelZuulProperties = sentinelZuulProperties; + } + + @Override + public boolean shouldFilter() { + return sentinelZuulProperties.isEnabled(); + } + +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelErrorFilter.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelErrorFilter.java new file mode 100644 index 00000000..dc192dba --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelErrorFilter.java @@ -0,0 +1,81 @@ +/* + * 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.zuul.filters; + +import com.alibaba.csp.sentinel.Tracer; +import com.alibaba.csp.sentinel.adapter.zuul.properties.SentinelZuulProperties; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.exception.ZuulException; + +import static com.alibaba.csp.sentinel.adapter.zuul.constants.ZuulConstant.ERROR_TYPE; + +/** + * This filter track routing exception and exit entry; + * + * @author tiger + */ +public class SentinelErrorFilter extends AbstractSentinelFilter { + + public SentinelErrorFilter(SentinelZuulProperties sentinelZuulProperties) { + super(sentinelZuulProperties); + } + + @Override + public String filterType() { + return ERROR_TYPE; + } + + @Override + public boolean shouldFilter() { + RequestContext ctx = RequestContext.getCurrentContext(); + return getSentinelZuulProperties().isEnabled() && ctx.getThrowable() != null; + } + + @Override + public int filterOrder() { + return getSentinelZuulProperties().getOrder().getError(); + } + + /** + * Trace not {@link BlockException} ex. + * While loop will exit all entries, + * Case serviceId and URL entry twice in {@link SentinelPreFilter}. + * The ContextUtil.getContext().getCurEntry() will exit from inner to outer. + */ + @Override + public Object run() throws ZuulException { + try { + RequestContext ctx = RequestContext.getCurrentContext(); + Throwable throwable = ctx.getThrowable(); + if (throwable != null) { + if (!BlockException.isBlockException(throwable)) { + Tracer.trace(throwable.getCause()); + RecordLog.info("[Sentinel Error Filter] Trace cause", throwable.getCause()); + } + } + } finally { + while (ContextUtil.getContext() != null && ContextUtil.getContext().getCurEntry() != null) { + ContextUtil.getContext().getCurEntry().exit(); + } + ContextUtil.exit(); + } + return null; + } +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPostFilter.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPostFilter.java new file mode 100644 index 00000000..9159489c --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPostFilter.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.zuul.filters; + +import com.alibaba.csp.sentinel.adapter.zuul.properties.SentinelZuulProperties; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.netflix.zuul.exception.ZuulException; + +import static com.alibaba.csp.sentinel.adapter.zuul.constants.ZuulConstant.POST_TYPE; + +/** + * This filter do success routing RT statistic and exit {@link com.alibaba.csp.sentinel.Entry} + * + * @author tiger + */ +public class SentinelPostFilter extends AbstractSentinelFilter { + + public SentinelPostFilter(SentinelZuulProperties sentinelZuulProperties) { + super(sentinelZuulProperties); + } + + @Override + public String filterType() { + return POST_TYPE; + } + + @Override + public int filterOrder() { + return getSentinelZuulProperties().getOrder().getPost(); + } + + /** + * While loop will exit all entries, + * Case serviceId and URL entry twice in {@link SentinelPreFilter}. + * The ContextUtil.getContext().getCurEntry() will exit from inner to outer. + */ + @Override + public Object run() throws ZuulException { + while (ContextUtil.getContext() != null && ContextUtil.getContext().getCurEntry() != null) { + ContextUtil.getContext().getCurEntry().exit(); + } + ContextUtil.exit(); + return null; + } +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPreFilter.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPreFilter.java new file mode 100644 index 00000000..7ef2c787 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPreFilter.java @@ -0,0 +1,112 @@ +/* + * 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.zuul.filters; + +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.adapter.zuul.fallback.*; +import com.alibaba.csp.sentinel.adapter.zuul.properties.SentinelZuulProperties; +import com.alibaba.csp.sentinel.adapter.zuul.util.FilterUtil; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.exception.ZuulException; + +import javax.servlet.http.HttpServletRequest; + +import static com.alibaba.csp.sentinel.adapter.zuul.constants.ZuulConstant.PRE_TYPE; +import static com.alibaba.csp.sentinel.adapter.zuul.constants.ZuulConstant.SERVICE_ID_KEY; +import static com.alibaba.csp.sentinel.adapter.zuul.constants.ZuulConstant.ZUUL_DEFAULT_CONTEXT; + +/** + * This pre filter get an entry of resource,the first order is ServiceId, then API Path. + * When get a BlockException run fallback logic. + * + * @author tiger + */ +public class SentinelPreFilter extends AbstractSentinelFilter { + + private final UrlCleaner urlCleaner; + + private final RequestOriginParser requestOriginParser; + + public SentinelPreFilter(SentinelZuulProperties sentinelZuulProperties, + UrlCleaner urlCleaner, + RequestOriginParser requestOriginParser) { + super(sentinelZuulProperties); + AssertUtil.notNull(urlCleaner, "UrlCleaner can not be null"); + AssertUtil.notNull(requestOriginParser, "RequestOriginParser can not be null"); + this.urlCleaner = urlCleaner; + this.requestOriginParser = requestOriginParser; + } + + @Override + public String filterType() { + return PRE_TYPE; + } + + /** + * This run before route filter so we can get more accurate RT time. + */ + @Override + public int filterOrder() { + return getSentinelZuulProperties().getOrder().getPre(); + } + + @Override + public Object run() throws ZuulException { + RequestContext ctx = RequestContext.getCurrentContext(); + String origin = parseOrigin(ctx.getRequest()); + String serviceTarget = (String) ctx.get(SERVICE_ID_KEY); + // When serviceId blocked first get the service level fallback provider. + String fallBackRoute = serviceTarget; + try { + if (StringUtil.isNotEmpty(serviceTarget)) { + RecordLog.info(String.format("[Sentinel Pre Filter] Origin: %s enter ServiceId: %s", origin, serviceTarget)); + ContextUtil.enter(serviceTarget, origin); + SphU.entry(serviceTarget, EntryType.IN); + } else { + RecordLog.info("[Sentinel Pre Filter] ServiceId is empty"); + ContextUtil.enter(ZUUL_DEFAULT_CONTEXT, origin); + } + String uriTarget = FilterUtil.filterTarget(ctx.getRequest()); + // Clean and unify the URL. + // For REST APIs, you have to clean the URL (e.g. `/foo/1` and `/foo/2` -> `/foo/:id`), or + // the amount of context and resources will exceed the threshold. + uriTarget = urlCleaner.clean(uriTarget); + fallBackRoute = uriTarget; + RecordLog.info(String.format("[Sentinel Pre Filter] Origin: %s enter Uri Path: %s", origin, uriTarget)); + SphU.entry(uriTarget, EntryType.IN); + } catch (BlockException ex) { + RecordLog.warn(String.format("[Sentinel Pre Filter] Block Exception when Origin: %s enter fall back route: %s", origin, fallBackRoute), ex); + ZuulBlockFallbackProvider zuulBlockFallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(fallBackRoute); + BlockResponse blockResponse = zuulBlockFallbackProvider.fallbackResponse(fallBackRoute, ex); + // prevent routing from running + ctx.setRouteHost(null); + ctx.set(SERVICE_ID_KEY, null); + ctx.setResponseBody(blockResponse.toString()); + } + return null; + } + + private String parseOrigin(HttpServletRequest request) { + return requestOriginParser.parseOrigin(request); + } +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/properties/SentinelZuulProperties.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/properties/SentinelZuulProperties.java new file mode 100644 index 00000000..8e7681ed --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/properties/SentinelZuulProperties.java @@ -0,0 +1,80 @@ +/* + * 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.zuul.properties; + +import static com.alibaba.csp.sentinel.adapter.zuul.constants.ZuulConstant.SEND_RESPONSE_FILTER_ORDER; + +/** + * Sentinel Spring Cloud Zuul AutoConfiguration property. + * + * @author tiger + */ +public class SentinelZuulProperties { + + private boolean enabled = false; + + private Order order = new Order(); + + public static class Order { + + private int post = SEND_RESPONSE_FILTER_ORDER - 10; + + private int pre = 10000; + + private int error = -1; + + public int getPost() { + return post; + } + + public void setPost(int post) { + this.post = post; + } + + public int getPre() { + return pre; + } + + public void setPre(int pre) { + this.pre = pre; + } + + public int getError() { + return error; + } + + public void setError(int error) { + this.error = error; + } + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/util/FilterUtil.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/util/FilterUtil.java new file mode 100755 index 00000000..cc00ccee --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/zuul/util/FilterUtil.java @@ -0,0 +1,157 @@ +/* + * 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.zuul.util; + +import com.alibaba.csp.sentinel.util.StringUtil; + +import javax.servlet.http.HttpServletRequest; + +/** + * Util class for web servlet filter. + * This is same as servlet adapter util class + * + * @author tiger + */ +public final class FilterUtil { + + public static String filterTarget(HttpServletRequest request) { + String pathInfo = getResourcePath(request); + if (!pathInfo.startsWith("/")) { + pathInfo = "/" + pathInfo; + } + + if ("/".equals(pathInfo)) { + return pathInfo; + } + + // Note: pathInfo should be converted to camelCase style. + int lastSlashIndex = pathInfo.lastIndexOf("/"); + + if (lastSlashIndex >= 0) { + pathInfo = pathInfo.substring(0, lastSlashIndex) + "/" + + StringUtil.trim(pathInfo.substring(lastSlashIndex + 1)); + } else { + pathInfo = "/" + StringUtil.trim(pathInfo); + } + + return pathInfo; + } + + private static String getResourcePath(HttpServletRequest request) { + String pathInfo = normalizeAbsolutePath(request.getPathInfo(), false); + String servletPath = normalizeAbsolutePath(request.getServletPath(), pathInfo.length() != 0); + + return servletPath + pathInfo; + } + + private static String normalizeAbsolutePath(String path, boolean removeTrailingSlash) throws IllegalStateException { + return normalizePath(path, true, false, removeTrailingSlash); + } + + private static String normalizePath(String path, boolean forceAbsolute, boolean forceRelative, + boolean removeTrailingSlash) throws IllegalStateException { + char[] pathChars = StringUtil.trimToEmpty(path).toCharArray(); + int length = pathChars.length; + + // Check path and slash. + boolean startsWithSlash = false; + boolean endsWithSlash = false; + + if (length > 0) { + char firstChar = pathChars[0]; + char lastChar = pathChars[length - 1]; + + startsWithSlash = firstChar == '/' || firstChar == '\\'; + endsWithSlash = lastChar == '/' || lastChar == '\\'; + } + + StringBuilder buf = new StringBuilder(length); + boolean isAbsolutePath = forceAbsolute || !forceRelative && startsWithSlash; + int index = startsWithSlash ? 0 : -1; + int level = 0; + + if (isAbsolutePath) { + buf.append("/"); + } + + while (index < length) { + index = indexOfSlash(pathChars, index + 1, false); + + if (index == length) { + break; + } + + int nextSlashIndex = indexOfSlash(pathChars, index, true); + + String element = new String(pathChars, index, nextSlashIndex - index); + index = nextSlashIndex; + + // Ignore "." + if (".".equals(element)) { + continue; + } + + // Backtrack ".." + if ("..".equals(element)) { + if (level == 0) { + if (isAbsolutePath) { + throw new IllegalStateException(path); + } else { + buf.append("../"); + } + } else { + buf.setLength(pathChars[--level]); + } + + continue; + } + + pathChars[level++] = (char)buf.length(); + buf.append(element).append('/'); + } + + // remove the last "/" + if (buf.length() > 0) { + if (!endsWithSlash || removeTrailingSlash) { + buf.setLength(buf.length() - 1); + } + } + + return buf.toString(); + } + + private static int indexOfSlash(char[] chars, int beginIndex, boolean slash) { + int i = beginIndex; + + for (; i < chars.length; i++) { + char ch = chars[i]; + + if (slash) { + if (ch == '/' || ch == '\\') { + break; // if a slash + } + } else { + if (ch != '/' && ch != '\\') { + break; // if not a slash + } + } + } + + return i; + } + + private FilterUtil() {} +} diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackManagerTest.java b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackManagerTest.java new file mode 100644 index 00000000..81425be2 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackManagerTest.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.zuul.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-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackProviderTest.java b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackProviderTest.java new file mode 100644 index 00000000..f701d4a9 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/fallback/ZuulBlockFallbackProviderTest.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.zuul.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-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelErrorFilterTest.java b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelErrorFilterTest.java new file mode 100644 index 00000000..8f2bca89 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelErrorFilterTest.java @@ -0,0 +1,56 @@ +/* + * 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.zuul.filters; + +import com.alibaba.csp.sentinel.adapter.zuul.properties.SentinelZuulProperties; +import com.netflix.zuul.context.RequestContext; +import org.junit.Assert; +import org.junit.Test; + +import static com.alibaba.csp.sentinel.adapter.zuul.constants.ZuulConstant.ERROR_TYPE; + +/** + * @author tiger + */ +public class SentinelErrorFilterTest { + @Test + public void testFilterType() throws Exception { + SentinelZuulProperties properties = new SentinelZuulProperties(); + SentinelErrorFilter sentinelErrorFilter = new SentinelErrorFilter(properties); + Assert.assertEquals(sentinelErrorFilter.filterType(), ERROR_TYPE); + } + + @Test + public void testShouldFilter() { + SentinelZuulProperties properties = new SentinelZuulProperties(); + Assert.assertFalse(properties.isEnabled()); + properties.setEnabled(true); + SentinelErrorFilter sentinelErrorFilter = new SentinelErrorFilter(properties); + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.setThrowable(new RuntimeException()); + Assert.assertTrue(sentinelErrorFilter.shouldFilter()); + } + + @Test + public void testRun() throws Exception { + SentinelZuulProperties properties = new SentinelZuulProperties(); + SentinelErrorFilter sentinelErrorFilter = new SentinelErrorFilter(properties); + Object result = sentinelErrorFilter.run(); + Assert.assertNull(result); + } + +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPostFilterTest.java b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPostFilterTest.java new file mode 100644 index 00000000..6ccd6cae --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPostFilterTest.java @@ -0,0 +1,45 @@ +/* + * 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.zuul.filters; + +import com.alibaba.csp.sentinel.adapter.zuul.properties.SentinelZuulProperties; +import org.junit.Assert; +import org.junit.Test; + +import static com.alibaba.csp.sentinel.adapter.zuul.constants.ZuulConstant.POST_TYPE; + +/** + * @author tiger + */ +public class SentinelPostFilterTest { + + @Test + public void testFilterType() throws Exception { + SentinelZuulProperties properties = new SentinelZuulProperties(); + SentinelPostFilter sentinelPostFilter = new SentinelPostFilter(properties); + Assert.assertEquals(sentinelPostFilter.filterType(), POST_TYPE); + } + + @Test + public void testRun() throws Exception { + SentinelZuulProperties properties = new SentinelZuulProperties(); + SentinelPostFilter sentinelPostFilter = new SentinelPostFilter(properties); + Object result = sentinelPostFilter.run(); + Assert.assertNull(result); + } + +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPreFilterTest.java b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPreFilterTest.java new file mode 100644 index 00000000..ab71e5cf --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/zuul/filters/SentinelPreFilterTest.java @@ -0,0 +1,106 @@ +/* + * 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.zuul.filters; + +import com.alibaba.csp.sentinel.adapter.zuul.fallback.DefaultRequestOriginParser; +import com.alibaba.csp.sentinel.adapter.zuul.fallback.RequestOriginParser; +import com.alibaba.csp.sentinel.adapter.zuul.fallback.UrlCleaner; +import com.alibaba.csp.sentinel.adapter.zuul.properties.SentinelZuulProperties; +import com.alibaba.csp.sentinel.slots.block.flow.FlowException; +import com.netflix.zuul.context.RequestContext; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import javax.servlet.http.HttpServletRequest; + +import static com.alibaba.csp.sentinel.adapter.zuul.constants.ZuulConstant.PRE_TYPE; +import static com.alibaba.csp.sentinel.adapter.zuul.constants.ZuulConstant.SERVICE_ID_KEY; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; + +/** + * @author tiger + */ +public class SentinelPreFilterTest { + + private String SERVICE_ID = "servicea"; + + private String URI = "/servicea/test"; + + @Mock + private HttpServletRequest httpServletRequest; + + @Mock + private UrlCleaner urlCleaner; + + private final RequestOriginParser requestOriginParser = new DefaultRequestOriginParser(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(httpServletRequest.getContextPath()).thenReturn(""); + when(httpServletRequest.getPathInfo()).thenReturn(URI); + RequestContext requestContext = new RequestContext(); + requestContext.set(SERVICE_ID_KEY, SERVICE_ID); + requestContext.setRequest(httpServletRequest); + RequestContext.testSetCurrentContext(requestContext); + } + + @Test + public void testFilterType() throws Exception { + SentinelZuulProperties properties = new SentinelZuulProperties(); + SentinelPreFilter sentinelPreFilter = new SentinelPreFilter(properties, urlCleaner, requestOriginParser); + Assert.assertEquals(sentinelPreFilter.filterType(), PRE_TYPE); + } + + @Test + public void testRun() throws Exception { + RequestContext ctx = RequestContext.getCurrentContext(); + SentinelZuulProperties properties = new SentinelZuulProperties(); + SentinelPreFilter sentinelPreFilter = new SentinelPreFilter(properties, urlCleaner, requestOriginParser); + given(urlCleaner.clean(URI)).willReturn(URI); + sentinelPreFilter.run(); + Assert.assertNull(ctx.getRouteHost()); + Assert.assertEquals(ctx.get(SERVICE_ID_KEY), SERVICE_ID); + } + + @Test + public void testServiceFallBackRun() throws Exception { + RequestContext ctx = RequestContext.getCurrentContext(); + SentinelZuulProperties properties = new SentinelZuulProperties(); + properties.setEnabled(true); + SentinelPreFilter sentinelPreFilter = new SentinelPreFilter(properties, urlCleaner, requestOriginParser); + + given(urlCleaner.clean(URI)).willAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + throw new FlowException("flow ex"); + } + } + ); + sentinelPreFilter.run(); + Assert.assertNull(ctx.getRouteHost()); + Assert.assertNull(ctx.get(SERVICE_ID_KEY)); + } + +} \ No newline at end of file