diff --git a/sentinel-adapter/pom.xml b/sentinel-adapter/pom.xml index 0d90b1ad..beab90ee 100755 --- a/sentinel-adapter/pom.xml +++ b/sentinel-adapter/pom.xml @@ -28,6 +28,7 @@ sentinel-api-gateway-adapter-common sentinel-spring-cloud-gateway-adapter sentinel-spring-webmvc-adapter + sentinel-spring-webmvc-6x-adapter sentinel-zuul2-adapter sentinel-okhttp-adapter sentinel-jax-rs-adapter diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/README.md b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/README.md new file mode 100644 index 00000000..cfbeb2da --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/README.md @@ -0,0 +1,107 @@ +# Sentinel Spring MVC Adapter + +## Introduction + +Sentinel provides integration for Spring Web to enable flow control for web requests. + +Add the following dependency in `pom.xml` (if you are using Maven): + +```xml + + com.alibaba.csp + sentinel-spring-webmvc-adapter + x.y.z + +``` + +Then we could add a configuration bean to configure the interceptor: + +```java +@Configuration +public class InterceptorConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + SentinelWebMvcConfig config = new SentinelWebMvcConfig(); + // Enable the HTTP method prefix. + config.setHttpMethodSpecify(true); + // Add to the interceptor list. + registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); + } +} +``` + +Then Sentinel will extract URL patterns defined in Web Controller as the web resource (e.g. `/foo/{id}`). + +## Configuration + +### Block handling + +Sentinel Spring Web adapter provides a `BlockExceptionHandler` interface to handle the blocked requests. +We could set the handler via `SentinelWebMvcTotalConfig#setBlockExceptionHandler()` method. + +By default the interceptor will throw out the `BlockException`. +We need to set a global exception handler function in Spring to handle it. An example: + +```java +@ControllerAdvice +@Order(0) +public class SentinelBlockExceptionHandlerConfig { + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + @ExceptionHandler(BlockException.class) + @ResponseBody + public String sentinelBlockHandler(BlockException e) { + AbstractRule rule = e.getRule(); + logger.info("Blocked by Sentinel: {}", rule.toString()); + return "Blocked by Sentinel"; + } +} +``` + +We've provided a `DefaultBlockExceptionHandler`. When a request is blocked, the handler will return a default page +indicating the request is rejected (`Blocked by Sentinel (flow limiting)`). +The HTTP status code of the default block page is **429 (Too Many Requests)**. + +We could also implement our implementation of the `BlockExceptionHandler` interface and +set to the config object. An example: + +```java +SentinelWebMvcConfig config = new SentinelWebMvcConfig(); +config.setBlockExceptionHandler((request, response, e) -> { + String resourceName = e.getRule().getResource(); + // Depending on your situation, you can choose to process or throw + if ("/hello".equals(resourceName)) { + // Do something ...... + response.getWriter().write("Blocked by Sentinel"); + } else { + // Handle it in global exception handling + throw e; + } +}); +``` + +### Customized configuration + +- Common configuration in `SentinelWebMvcConfig` and `SentinelWebMvcTotalConfig`: + +| name | description | type | default value | +|------|------------|------|-------| +| `blockExceptionHandler`| The handler that handles the block request | `BlockExceptionHandler` | null (throw out the BlockException) | +| `originParser` | Extracting request origin (e.g. IP or appName from HTTP Header) from HTTP request | `RequestOriginParser` | - | + +- `SentinelWebMvcConfig` configuration: + +| name | description | type | default value | +|------|------------|------|-------| +| urlCleaner | The `UrlCleaner` interface is designed for clean and unify the URL resource. | `UrlCleaner` | - | +| requestAttributeName | Attribute key in request used by Sentinel (internal) | `String` | `$$sentinel_spring_web_entry_attr` | +| httpMethodSpecify | Specify whether the URL resource name should contain the HTTP method prefix (e.g. `POST:`). | `boolean` | `false` | +| webContextUnify | Specify whether unify web context(i.e. use the default context name). | `boolean` | `true` | + +- `SentinelWebMvcTotalConfig` configuration: + +| name | description | type | default value | +|------|------------|------|-------| +| totalResourceName | The resource name in `SentinelTotalInterceptor` | `String` | `spring-mvc-total-url-request` | +| requestAttributeName | Attribute key in request used by Sentinel (internal) | `String` | `$$sentinel_spring_web_total_entry_attr` | \ No newline at end of file diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/pom.xml b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/pom.xml new file mode 100644 index 00000000..81d95ffe --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/pom.xml @@ -0,0 +1,80 @@ + + + + com.alibaba.csp + sentinel-adapter + 2.0.0-SNAPSHOT + + 4.0.0 + + sentinel-spring-webmvc-6x-adapter + jar + + + 6.0.2 + 3.0.0 + 6.0.0 + 17 + 17 + + + + + com.alibaba.csp + sentinel-core + + + jakarta.servlet + jakarta.servlet-api + ${servlet.api.version} + provided + + + org.springframework + spring-webmvc + ${spring.version} + provided + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + test + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + com.alibaba + fastjson + test + + + junit + junit + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.version} + + ${java.source.version} + ${java.target.version} + ${java.encoding} + + + + + + \ No newline at end of file diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java new file mode 100644 index 00000000..b4bfe2da --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java @@ -0,0 +1,200 @@ +/* + * 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.spring.webmvc; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.ResourceTypeConstants; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.Tracer; +import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.BaseWebMvcConfig; +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 org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +/** + * Since request may be reprocessed in flow if any forwarding or including or other action + * happened (see {@link jakarta.servlet.ServletRequest#getDispatcherType()}) we will only + * deal with the initial request. So we use reference count to track in + * dispathing "onion" though which we could figure out whether we are in initial type "REQUEST". + * That means the sub-requests which we rarely meet in practice will NOT be recorded in Sentinel. + *

+ * How to implement a forward sub-request in your action: + *

+ * initalRequest() {
+ *     ModelAndView mav = new ModelAndView();
+ *     mav.setViewName("another");
+ *     return mav;
+ * }
+ * 
+ * + * @author kaizi2009 + * @since 1.7.1 + */ +public abstract class AbstractSentinelInterceptor implements HandlerInterceptor { + + public static final String SENTINEL_SPRING_WEB_CONTEXT_NAME = "sentinel_spring_web_context"; + private static final String EMPTY_ORIGIN = ""; + + private final BaseWebMvcConfig baseWebMvcConfig; + + public AbstractSentinelInterceptor(BaseWebMvcConfig config) { + AssertUtil.notNull(config, "BaseWebMvcConfig should not be null"); + AssertUtil.assertNotBlank(config.getRequestAttributeName(), "requestAttributeName should not be blank"); + this.baseWebMvcConfig = config; + } + + /** + * @param request + * @param rcKey + * @param step + * @return reference count after increasing (initial value as zero to be increased) + */ + private Integer increaseReferece(HttpServletRequest request, String rcKey, int step) { + Object obj = request.getAttribute(rcKey); + + if (obj == null) { + // initial + obj = Integer.valueOf(0); + } + + Integer newRc = (Integer)obj + step; + request.setAttribute(rcKey, newRc); + return newRc; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + try { + String resourceName = getResourceName(request); + + if (StringUtil.isEmpty(resourceName)) { + return true; + } + + if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) { + return true; + } + + // Parse the request origin using registered origin parser. + String origin = parseOrigin(request); + String contextName = getContextName(request); + ContextUtil.enter(contextName, origin); + Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); + request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry); + return true; + } catch (BlockException e) { + try { + handleBlockException(request, response, e); + } finally { + ContextUtil.exit(); + } + return false; + } + } + + /** + * Return the resource name of the target web resource. + * + * @param request web request + * @return the resource name of the target web resource. + */ + protected abstract String getResourceName(HttpServletRequest request); + + /** + * Return the context name of the target web resource. + * + * @param request web request + * @return the context name of the target web resource. + */ + protected String getContextName(HttpServletRequest request) { + return SENTINEL_SPRING_WEB_CONTEXT_NAME; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, + Object handler, Exception ex) throws Exception { + if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), -1) != 0) { + return; + } + + Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName()); + if (entry == null) { + // should not happen + RecordLog.warn("[{}] No entry found in request, key: {}", + getClass().getSimpleName(), baseWebMvcConfig.getRequestAttributeName()); + return; + } + + traceExceptionAndExit(entry, ex); + removeEntryInRequest(request); + ContextUtil.exit(); + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, + ModelAndView modelAndView) throws Exception { + } + + protected Entry getEntryInRequest(HttpServletRequest request, String attrKey) { + Object entryObject = request.getAttribute(attrKey); + return entryObject == null ? null : (Entry)entryObject; + } + + protected void removeEntryInRequest(HttpServletRequest request) { + request.removeAttribute(baseWebMvcConfig.getRequestAttributeName()); + } + + protected void traceExceptionAndExit(Entry entry, Exception ex) { + if (entry != null) { + if (ex != null) { + Tracer.traceEntry(ex, entry); + } + entry.exit(); + } + } + + protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e) + throws Exception { + if (baseWebMvcConfig.getBlockExceptionHandler() != null) { + baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e); + } else { + // Throw BlockException directly. Users need to handle it in Spring global exception handler. + throw e; + } + } + + protected String parseOrigin(HttpServletRequest request) { + String origin = EMPTY_ORIGIN; + if (baseWebMvcConfig.getOriginParser() != null) { + origin = baseWebMvcConfig.getOriginParser().parseOrigin(request); + if (StringUtil.isEmpty(origin)) { + return EMPTY_ORIGIN; + } + } + return origin; + } + +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java new file mode 100644 index 00000000..37ffd703 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java @@ -0,0 +1,77 @@ +/* + * 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.spring.webmvc; + +import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig; +import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner; + +import jakarta.servlet.http.HttpServletRequest; + +import com.alibaba.csp.sentinel.util.StringUtil; +import org.springframework.web.servlet.HandlerMapping; + +/** + * Spring Web MVC interceptor that integrates with Sentinel. + * + * @author kaizi2009 + * @since 1.7.1 + */ +public class SentinelWebInterceptor extends AbstractSentinelInterceptor { + + private final SentinelWebMvcConfig config; + + public SentinelWebInterceptor() { + this(new SentinelWebMvcConfig()); + } + + public SentinelWebInterceptor(SentinelWebMvcConfig config) { + super(config); + if (config == null) { + // Use the default config by default. + this.config = new SentinelWebMvcConfig(); + } else { + this.config = config; + } + } + + @Override + protected String getResourceName(HttpServletRequest request) { + // Resolve the Spring Web URL pattern from the request attribute. + Object resourceNameObject = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); + if (resourceNameObject == null || !(resourceNameObject instanceof String)) { + return null; + } + String resourceName = (String) resourceNameObject; + UrlCleaner urlCleaner = config.getUrlCleaner(); + if (urlCleaner != null) { + resourceName = urlCleaner.clean(resourceName); + } + // Add method specification if necessary + if (StringUtil.isNotEmpty(resourceName) && config.isHttpMethodSpecify()) { + resourceName = request.getMethod().toUpperCase() + ":" + resourceName; + } + return resourceName; + } + + @Override + protected String getContextName(HttpServletRequest request) { + if (config.isWebContextUnify()) { + return super.getContextName(request); + } + + return getResourceName(request); + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebTotalInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebTotalInterceptor.java new file mode 100644 index 00000000..e1751c50 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebTotalInterceptor.java @@ -0,0 +1,50 @@ +/* + * 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.spring.webmvc; + +import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcTotalConfig; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * The web interceptor for all requests, which will unify all URL as + * a single resource name (configured in {@link SentinelWebMvcTotalConfig}). + * + * @author kaizi2009 + * @since 1.7.1 + */ +public class SentinelWebTotalInterceptor extends AbstractSentinelInterceptor { + + private final SentinelWebMvcTotalConfig config; + + public SentinelWebTotalInterceptor(SentinelWebMvcTotalConfig config) { + super(config); + if (config == null) { + this.config = new SentinelWebMvcTotalConfig(); + } else { + this.config = config; + } + } + + public SentinelWebTotalInterceptor() { + this(new SentinelWebMvcTotalConfig()); + } + + @Override + protected String getResourceName(HttpServletRequest request) { + return config.getTotalResourceName(); + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java new file mode 100644 index 00000000..7e9c87f6 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java @@ -0,0 +1,40 @@ +/* + * 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.spring.webmvc.callback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * Handler for the blocked request. + * + * @author kaizi2009 + */ +public interface BlockExceptionHandler { + + /** + * Handle the request when blocked. + * + * @param request Servlet request + * @param response Servlet response + * @param e the block exception + * @throws Exception users may throw out the BlockException or other error occurs + */ + void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception; + +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java new file mode 100644 index 00000000..9b54974d --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java @@ -0,0 +1,42 @@ +/* + * 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.spring.webmvc.callback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.PrintWriter; + +/** + * Default handler for the blocked request. + * + * @author kaizi2009 + */ +public class DefaultBlockExceptionHandler implements BlockExceptionHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { + // Return 429 (Too Many Requests) by default. + response.setStatus(429); + + PrintWriter out = response.getWriter(); + out.print("Blocked by Sentinel (flow limiting)"); + out.flush(); + out.close(); + } + +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/RequestOriginParser.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/RequestOriginParser.java new file mode 100644 index 00000000..9bb34270 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/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.spring.webmvc.callback; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * The origin parser parses request origin (e.g. IP, user, appName) from HTTP request. + * + * @author kaizi2009 + */ +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-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java new file mode 100644 index 00000000..8541d097 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java @@ -0,0 +1,32 @@ +/* + * 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.spring.webmvc.callback; + +/** + * Unify the resource target. + * + * @author kaizi2009 + */ +public interface UrlCleaner { + + /** + * Unify the resource target. + * + * @param originUrl the original URL + * @return the unified resource name + */ + String clean(String originUrl); +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java new file mode 100644 index 00000000..e1bd1542 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java @@ -0,0 +1,67 @@ +/* + * 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 + * + * 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.spring.webmvc.config; + +import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler; +import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser; + +/** + * Common base configuration for Spring Web MVC adapter. + * + * @author kaizi2009 + * @since 1.7.1 + */ +public abstract class BaseWebMvcConfig { + + protected String requestAttributeName; + protected String requestRefName; + protected BlockExceptionHandler blockExceptionHandler; + protected RequestOriginParser originParser; + + public String getRequestAttributeName() { + return requestAttributeName; + } + + public void setRequestAttributeName(String requestAttributeName) { + this.requestAttributeName = requestAttributeName; + this.requestRefName = this.requestAttributeName + "-rc"; + } + + /** + * Paired with attr name used to track reference count. + * + * @return + */ + public String getRequestRefName() { + return requestRefName; + } + + public BlockExceptionHandler getBlockExceptionHandler() { + return blockExceptionHandler; + } + + public void setBlockExceptionHandler(BlockExceptionHandler blockExceptionHandler) { + this.blockExceptionHandler = blockExceptionHandler; + } + + public RequestOriginParser getOriginParser() { + return originParser; + } + + public void setOriginParser(RequestOriginParser originParser) { + this.originParser = originParser; + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java new file mode 100644 index 00000000..c94ff8c2 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java @@ -0,0 +1,88 @@ +/* + * 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.spring.webmvc.config; + +import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner; + +/** + * @author kaizi2009 + * @since 1.7.1 + */ +public class SentinelWebMvcConfig extends BaseWebMvcConfig { + + public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "$$sentinel_spring_web_entry_attr"; + + /** + * Specify the URL cleaner that unifies the URL resources. + */ + private UrlCleaner urlCleaner; + + /** + * Specify whether the URL resource name should contain the HTTP method prefix (e.g. {@code POST:}). + */ + private boolean httpMethodSpecify; + + /** + * Specify whether unify web context(i.e. use the default context name), and is true by default. + * + * @since 1.7.2 + */ + private boolean webContextUnify = true; + + public SentinelWebMvcConfig() { + super(); + setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME); + } + + public UrlCleaner getUrlCleaner() { + return urlCleaner; + } + + public SentinelWebMvcConfig setUrlCleaner(UrlCleaner urlCleaner) { + this.urlCleaner = urlCleaner; + return this; + } + + public boolean isHttpMethodSpecify() { + return httpMethodSpecify; + } + + public SentinelWebMvcConfig setHttpMethodSpecify(boolean httpMethodSpecify) { + this.httpMethodSpecify = httpMethodSpecify; + return this; + } + + public boolean isWebContextUnify() { + return webContextUnify; + } + + public SentinelWebMvcConfig setWebContextUnify(boolean webContextUnify) { + this.webContextUnify = webContextUnify; + return this; + } + + @Override + public String toString() { + return "SentinelWebMvcConfig{" + + "urlCleaner=" + urlCleaner + + ", httpMethodSpecify=" + httpMethodSpecify + + ", webContextUnify=" + webContextUnify + + ", requestAttributeName='" + requestAttributeName + '\'' + + ", blockExceptionHandler=" + blockExceptionHandler + + ", originParser=" + originParser + + '}'; + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java new file mode 100644 index 00000000..dc8b4af0 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java @@ -0,0 +1,52 @@ +/* + * 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.spring.webmvc.config; + +/** + * @author kaizi2009 + * @since 1.7.1 + */ +public class SentinelWebMvcTotalConfig extends BaseWebMvcConfig { + + public static final String DEFAULT_TOTAL_RESOURCE_NAME = "spring-mvc-total-url-request"; + public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "$$sentinel_spring_web_total_entry_attr"; + + private String totalResourceName = DEFAULT_TOTAL_RESOURCE_NAME; + + public SentinelWebMvcTotalConfig() { + super(); + setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME); + } + + public String getTotalResourceName() { + return totalResourceName; + } + + public SentinelWebMvcTotalConfig setTotalResourceName(String totalResourceName) { + this.totalResourceName = totalResourceName; + return this; + } + + @Override + public String toString() { + return "SentinelWebMvcTotalConfig{" + + "totalResourceName='" + totalResourceName + '\'' + + ", requestAttributeName='" + requestAttributeName + '\'' + + ", blockExceptionHandler=" + blockExceptionHandler + + ", originParser=" + originParser + + '}'; + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/ResultWrapper.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/ResultWrapper.java new file mode 100644 index 00000000..66121fcd --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/ResultWrapper.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.spring.webmvc; + +import com.alibaba.fastjson.JSONObject; + +/** + * @author kaizi2009 + */ +public class ResultWrapper { + + private Integer code; + private String message; + + public ResultWrapper(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public static ResultWrapper error() { + + return new ResultWrapper(-1, "System error"); + } + + public static ResultWrapper blocked() { + return new ResultWrapper(-2, "Blocked by Sentinel"); + } + + public String toJsonString() { + return JSONObject.toJSONString(this); + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java new file mode 100644 index 00000000..f1ddb937 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java @@ -0,0 +1,158 @@ +/* + * 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.spring.webmvc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.alibaba.csp.sentinel.node.ClusterNode; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import com.alibaba.csp.sentinel.util.StringUtil; + +import java.util.Collections; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +/** + * @author kaizi2009 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestApplication.class) +@AutoConfigureMockMvc +public class SentinelSpringMvcIntegrationTest { + + private static final String HELLO_STR = "Hello!"; + @Autowired + private MockMvc mvc; + + @Test + public void testBase() throws Exception { + String url = "/hello"; + this.mvc.perform(get(url)) + .andExpect(status().isOk()) + .andExpect(content().string(HELLO_STR)); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + } + + @Test + public void testOriginParser() throws Exception { + String springMvcPathVariableUrl = "/foo/{id}"; + String limitOrigin = "userA"; + final String headerName = "S-User"; + configureRulesFor(springMvcPathVariableUrl, 0, limitOrigin); + + // This will be passed since the caller is different: userB + this.mvc.perform(get("/foo/1").accept(MediaType.TEXT_PLAIN).header(headerName, "userB")) + .andExpect(status().isOk()) + .andExpect(content().string("foo 1")); + + // This will be blocked since the caller is same: userA + this.mvc.perform( + get("/foo/2").accept(MediaType.APPLICATION_JSON).header(headerName, limitOrigin)) + .andExpect(status().isOk()) + .andExpect(content().json(ResultWrapper.blocked().toJsonString())); + + // This will be passed since the caller is different: "" + this.mvc.perform(get("/foo/3").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string("foo 3")); + + FlowRuleManager.loadRules(null); + } + + @Test + public void testTotalInterceptor() throws Exception { + String url = "/hello"; + String totalTarget = "my_spring_mvc_total_url_request"; + for (int i = 0; i < 3; i++) { + this.mvc.perform(get(url)) + .andExpect(status().isOk()) + .andExpect(content().string(HELLO_STR)); + } + ClusterNode cn = ClusterBuilderSlot.getClusterNode(totalTarget); + assertNotNull(cn); + assertEquals(3, cn.passQps(), 0.01); + } + + @Test + public void testRuntimeException() throws Exception { + String url = "/runtimeException"; + configureExceptionRulesFor(url, 3, null); + int repeat = 3; + for (int i = 0; i < repeat; i++) { + this.mvc.perform(get(url)) + .andExpect(status().isOk()) + .andExpect(content().string(ResultWrapper.error().toJsonString())); + ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); + assertNotNull(cn); + assertEquals(i + 1, cn.passQps(), 0.01); + } + + // This will be blocked and response json. + this.mvc.perform(get(url)) + .andExpect(status().isOk()) + .andExpect(content().string(ResultWrapper.blocked().toJsonString())); + ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); + assertNotNull(cn); + assertEquals(repeat, cn.passQps(), 0.01); + assertEquals(1, cn.blockRequest(), 1); + } + + private void configureRulesFor(String resource, int count, String limitApp) { + FlowRule rule = new FlowRule() + .setCount(count) + .setGrade(RuleConstant.FLOW_GRADE_QPS); + rule.setResource(resource); + if (StringUtil.isNotBlank(limitApp)) { + rule.setLimitApp(limitApp); + } + FlowRuleManager.loadRules(Collections.singletonList(rule)); + } + + private void configureExceptionRulesFor(String resource, int count, String limitApp) { + FlowRule rule = new FlowRule() + .setCount(count) + .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); + rule.setResource(resource); + if (StringUtil.isNotBlank(limitApp)) { + rule.setLimitApp(limitApp); + } + FlowRuleManager.loadRules(Collections.singletonList(rule)); + } + + @After + public void cleanUp() { + FlowRuleManager.loadRules(null); + ClusterBuilderSlot.resetClusterNodes(); + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptorTest.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptorTest.java new file mode 100644 index 00000000..506d9b2d --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptorTest.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.spring.webmvc; + +import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Eric Zhao + */ +public class SentinelWebInterceptorTest { + + @Test(expected = IllegalArgumentException.class) + public void testPassIllegalConfig() { + SentinelWebMvcConfig config = new SentinelWebMvcConfig(); + config.setRequestAttributeName(null); + SentinelWebInterceptor interceptor = new SentinelWebInterceptor(config); + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestApplication.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestApplication.java new file mode 100644 index 00000000..da9b6bba --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestApplication.java @@ -0,0 +1,31 @@ +/* + * 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.spring.webmvc; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * @author kaizi2009 + */ +@SpringBootApplication +public class TestApplication { + public static void main(String[] args) { + SpringApplication.run(TestApplication.class); + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java new file mode 100644 index 00000000..b0c4112b --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java @@ -0,0 +1,92 @@ +/* + * 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.spring.webmvc.config; + +import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor; +import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebTotalInterceptor; +import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler; +import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * Config sentinel interceptor + * + * @author kaizi2009 + */ +@Configuration +public class InterceptorConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + //Add sentinel interceptor + addSpringMvcInterceptor(registry); + + //If you want to sentinel the total flow, you can add total interceptor + addSpringMvcTotalInterceptor(registry); + } + + private void addSpringMvcInterceptor(InterceptorRegistry registry) { + //Config + SentinelWebMvcConfig config = new SentinelWebMvcConfig(); + + config.setBlockExceptionHandler(new BlockExceptionHandler() { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { + String resourceName = e.getRule().getResource(); + //Depending on your situation, you can choose to process or throw + if ("/hello".equals(resourceName)) { + //Do something ...... + //Write string or json string; + response.getWriter().write("/Blocked by sentinel"); + } else { + //Handle in global exception handling + throw e; + } + } + }); + + //Custom configuration if necessary + config.setHttpMethodSpecify(false); + config.setWebContextUnify(true); + config.setOriginParser(new RequestOriginParser() { + @Override + public String parseOrigin(HttpServletRequest request) { + return request.getHeader("S-user"); + } + }); + + //Add sentinel interceptor + registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); + } + + private void addSpringMvcTotalInterceptor(InterceptorRegistry registry) { + //Configure + SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig(); + + //Custom configuration if necessary + config.setRequestAttributeName("my_sentinel_spring_mvc_total_entity_container"); + config.setTotalResourceName("my_spring_mvc_total_url_request"); + + //Add sentinel interceptor + registry.addInterceptor(new SentinelWebTotalInterceptor(config)).addPathPatterns("/**"); + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java new file mode 100644 index 00000000..b8e46603 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java @@ -0,0 +1,54 @@ +/* + * 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.spring.webmvc.config; + +import com.alibaba.csp.sentinel.adapter.spring.webmvc.ResultWrapper; +import com.alibaba.csp.sentinel.slots.block.AbstractRule; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.Order; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Config 'BlockException' handler, handler it in spring veb 'ExceptionHandler' + * + * @author kaizi2009 + */ +@ControllerAdvice +@Order(0) +public class SentinelSpringMvcBlockHandlerConfig { + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + @ExceptionHandler(BlockException.class) + @ResponseBody + public ResultWrapper sentinelBlockHandler(BlockException e) { + AbstractRule rule = e.getRule(); + //Log + logger.info("Blocked by sentinel, {}", rule.toString()); + //Return object + return ResultWrapper.blocked(); + } + + @ExceptionHandler(Exception.class) + @ResponseBody + public ResultWrapper exceptionHandler(Exception e) { + logger.error("System error", e.getMessage()); + return new ResultWrapper(-1, "System error"); + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/controller/TestController.java b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/controller/TestController.java new file mode 100644 index 00000000..d8a42ab8 --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webmvc-6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/controller/TestController.java @@ -0,0 +1,55 @@ +/* + * 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.spring.webmvc.controller; + + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author kaizi2009 + */ +@RestController +public class TestController { + + @GetMapping("/hello") + public String apiHello() { + return "Hello!"; + } + + @GetMapping("/err") + public String apiError() { + return "Oops..."; + } + + @GetMapping("/foo/{id}") + public String apiFoo(@PathVariable("id") Long id) { + return "foo " + id; + } + + @GetMapping("/runtimeException") + public String runtimeException() { + int i = 1 / 0; + return "runtimeException"; + } + + @GetMapping("/exclude/{id}") + public String apiExclude(@PathVariable("id") Long id) { + return "Exclude " + id; + } + +}