From 20ad3c8f7931722cf73b68ef6a13b6cf24f7c920 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Wed, 27 Nov 2019 17:42:13 +0800 Subject: [PATCH] Improve code and demo for sentinel-spring-webmvc-adapter module Signed-off-by: Eric Zhao --- .../sentinel-spring-webmvc-adapter/README.md | 103 ++++++++---------- .../sentinel-spring-webmvc-adapter/pom.xml | 5 +- .../webmvc/AbstractSentinelInterceptor.java | 43 +++++--- ...eptor.java => SentinelWebInterceptor.java} | 31 ++---- ....java => SentinelWebTotalInterceptor.java} | 28 ++--- .../callback/BlockExceptionHandler.java | 12 +- .../DefaultBlockExceptionHandler.java | 5 +- .../spring/webmvc/callback/UrlCleaner.java | 8 +- .../webmvc/config/BaseWebMvcConfig.java | 6 +- .../webmvc/config/SentinelWebMvcConfig.java | 29 ++++- .../config/SentinelWebMvcTotalConfig.java | 23 ++-- ... => SentinelSpringMvcIntegrationTest.java} | 4 +- .../webmvc/SentinelWebInterceptorTest.java | 35 ++++++ .../webmvc/config/InterceptorConfig.java | 8 +- .../sentinel-demo-spring-webmvc/pom.xml | 74 ++++++------- .../spring/webmvc/WebMvcDemoApplication.java | 6 +- .../webmvc/config/InterceptorConfig.java | 38 +++---- .../SentinelSpringMvcBlockHandlerConfig.java | 14 ++- 18 files changed, 260 insertions(+), 212 deletions(-) rename sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/{SentinelInterceptor.java => SentinelWebInterceptor.java} (64%) rename sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/{SentinelTotalInterceptor.java => SentinelWebTotalInterceptor.java} (57%) rename sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/{TestInterceptor.java => SentinelSpringMvcIntegrationTest.java} (99%) create mode 100644 sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptorTest.java diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md b/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md index 1693c281..941daed4 100755 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md @@ -1,6 +1,8 @@ -# Sentinel Spring MVC Interceptor +# Sentinel Spring MVC Adapter -Sentinel provides Spring MVC Interceptor integration to enable flow control for web requests, And support url like '/foo/{id}' +## 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): @@ -12,7 +14,7 @@ Add the following dependency in `pom.xml` (if you are using Maven): ``` -Configure interceptor +Then we could add a configuration bean to configure the interceptor: ```java @Configuration @@ -20,96 +22,85 @@ 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) { - //Configure SentinelWebMvcConfig config = new SentinelWebMvcConfig(); - //Custom configuration if necessary + // Enable the HTTP method prefix. config.setHttpMethodSpecify(true); - config.setOriginParser(request -> request.getHeader("S-user")); - //Add sentinel interceptor - registry.addInterceptor(new SentinelInterceptor(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 SentinelTotalInterceptor(config)).addPathPatterns("/**"); + // Add to the interceptor list. + registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); } } ``` -Configure 'BlockException' handler, there are three options: -1. Global exception handling in spring MVC. +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 SentinelSpringMvcBlockHandlerConfig { +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()); + 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: -``` -2. Use `DefaultBlockExceptionHandler` ```java -//SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig(); -SentinelWebMvcConfig config = new SentinelWebMvcConfig(); -config.setBlockExceptionHandler(new DefaultBlockExceptionHandler()); -``` -3. `implements BlockExceptionHandler` -```java -//SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig(); 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 + // Depending on your situation, you can choose to process or throw if ("/hello".equals(resourceName)) { - //Do something ...... - //Write string or error page; - response.getWriter().write("Blocked by sentinel"); + // Do something ...... + response.getWriter().write("Blocked by Sentinel"); } else { - //Handle it in global exception handling + // Handle it in global exception handling throw e; } }); ``` -Configuration -- Common configuration in `SentinelWebMvcConfig` and `SentinelWebMvcTotalConfig` +### Customized configuration + +- Common configuration in `SentinelWebMvcConfig` and `SentinelWebMvcTotalConfig`: | name | description | type | default value | |------|------------|------|-------| -| blockExceptionHandler| The handler when blocked by sentinel, there are three options:
1. The default value is null, you can hanlde `BlockException` in spring MVC;
2.Use `DefaultBlockExceptionHandler`;
3. `implements BlockExceptionHandler` | `BlockExceptionHandler` | `null` | -| originParser | `RequestOriginParser` interface is useful for extracting request origin (e.g. IP or appName from HTTP Header) from HTTP request | `RequestOriginParser` | `null` | +| `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 +- `SentinelWebMvcConfig` configuration: | name | description | type | default value | |------|------------|------|-------| -| urlCleaner | The `UrlCleaner` interface is designed for clean and unify the URL resource. For REST APIs, you can to clean the URL resource (e.g. `/api/user/getById` and `/api/user/getByName` -> `/api/user/getBy*`), avoid the amount of context and will exceed the threshold | `UrlCleaner` | `null` | -| requestAttributeName | Attribute name in request used by sentinel, please check record log, if it is already used, please set | `String` | sentinel_spring_mvc_entry_container | -| httpMethodSpecify | Specify http method, for example: GET:/hello | `boolean` | `false` | +| 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` | - -`SentinelWebMvcTotalConfig` configuration +- `SentinelWebMvcTotalConfig` configuration: | name | description | type | default value | |------|------------|------|-------| -| totalResourceName | The resource name in `SentinelTotalInterceptor` | `String` | spring-mvc-total-url-request | -| requestAttributeName | Attribute name in request used by sentinel, please check record log, if it is already used, please set | `String` | sentinel_spring_mvc_total_entry_container | - +| 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-adapter/pom.xml b/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml index 012480d1..c8e49c76 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml @@ -3,13 +3,14 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - sentinel-adapter com.alibaba.csp - 1.7.0-SNAPSHOT + sentinel-adapter + 1.7.1-SNAPSHOT 4.0.0 sentinel-spring-webmvc-adapter + jar 5.1.8.RELEASE diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java index 4c4d2757..e9f0a39e 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java @@ -20,38 +20,47 @@ import javax.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; /** * @author kaizi2009 + * @since 1.7.1 */ public abstract class AbstractSentinelInterceptor implements HandlerInterceptor { - public static final String SPRING_MVC_CONTEXT_NAME = "spring_mvc_context"; + public static final String SENTINEL_SPRING_WEB_CONTEXT_NAME = "sentinel_spring_web_context"; private static final String EMPTY_ORIGIN = ""; - protected static final String COLON = ":"; - private BaseWebMvcConfig baseWebMvcConfig; + + 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; + } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { - + throws Exception { try { String resourceName = getResourceName(request); if (StringUtil.isNotEmpty(resourceName)) { // Parse the request origin using registered origin parser. String origin = parseOrigin(request); - ContextUtil.enter(SPRING_MVC_CONTEXT_NAME, origin); - Entry entry = SphU.entry(resourceName, EntryType.IN); + ContextUtil.enter(SENTINEL_SPRING_WEB_CONTEXT_NAME, origin); + Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); setEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName(), entry); } @@ -63,9 +72,10 @@ public abstract class AbstractSentinelInterceptor implements HandlerInterceptor } /** - * Get sentinel resource name. - * @param request - * @return + * 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); @@ -88,7 +98,8 @@ public abstract class AbstractSentinelInterceptor implements HandlerInterceptor protected void setEntryInRequest(HttpServletRequest request, String name, Entry entry) { Object attrVal = request.getAttribute(name); if (attrVal != null) { - RecordLog.warn(String.format("Already exist attribute name '%s' in request, please set `requestAttributeName`", name)); + RecordLog.warn("[{}] The attribute key '{0}' already exists in request, please set `requestAttributeName`", + getClass().getSimpleName(), name); } else { request.setAttribute(name, entry); } @@ -96,7 +107,7 @@ public abstract class AbstractSentinelInterceptor implements HandlerInterceptor protected Entry getEntryInRequest(HttpServletRequest request, String attrKey) { Object entryObject = request.getAttribute(attrKey); - return entryObject == null ? null : (Entry) entryObject; + return entryObject == null ? null : (Entry)entryObject; } protected void removeEntryInRequest(HttpServletRequest request) { @@ -112,11 +123,12 @@ public abstract class AbstractSentinelInterceptor implements HandlerInterceptor } } - protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { + protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e) + throws Exception { if (baseWebMvcConfig.getBlockExceptionHandler() != null) { baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e); } else { - //Throw BlockException, handle it in spring mvc + // Throw BlockException directly. Users need to handle it in Spring global exception handler. throw e; } } @@ -132,7 +144,4 @@ public abstract class AbstractSentinelInterceptor implements HandlerInterceptor return origin; } - protected void setBaseWebMvcConfig(BaseWebMvcConfig config) { - this.baseWebMvcConfig = config; - } } diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java similarity index 64% rename from sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelInterceptor.java rename to sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java index a9c5c34e..b01b2c35 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelInterceptor.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java @@ -17,7 +17,6 @@ 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 com.alibaba.csp.sentinel.log.RecordLog; import javax.servlet.http.HttpServletRequest; @@ -25,42 +24,32 @@ import com.alibaba.csp.sentinel.util.StringUtil; import org.springframework.web.servlet.HandlerMapping; /** - * Spring mvc interceptor that integrates with sentinel. + * Spring Web MVC interceptor that integrates with Sentinel. * * @author kaizi2009 + * @since 1.7.1 */ -public class SentinelInterceptor extends AbstractSentinelInterceptor { - private SentinelWebMvcConfig config; +public class SentinelWebInterceptor extends AbstractSentinelInterceptor { - public SentinelInterceptor(SentinelWebMvcConfig config) { - super(); - setConfig(config); - super.setBaseWebMvcConfig(config); - } + private final SentinelWebMvcConfig config; - public SentinelInterceptor() { + public SentinelWebInterceptor() { this(new SentinelWebMvcConfig()); } - public SentinelInterceptor setConfig(SentinelWebMvcConfig config) { + public SentinelWebInterceptor(SentinelWebMvcConfig config) { + super(config); if (config == null) { + // Use the default config by default. this.config = new SentinelWebMvcConfig(); - RecordLog.info("Config is null, use default config"); } else { this.config = config; } - RecordLog.info(String.format("SentinelInterceptor config: requestAttributeName=%s, originParser=%s, httpMethodSpecify=%s, blockExceptionHandler=%s, urlCleaner=%s", config.getRequestAttributeName(), config.getOriginParser(), config.isHttpMethodSpecify(), config.getBlockExceptionHandler(), config.getUrlCleaner())); - return this; } - /** - * Get target in HttpServletRequest - * - * @param request - * @return - */ @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; @@ -72,7 +61,7 @@ public class SentinelInterceptor extends AbstractSentinelInterceptor { } // Add method specification if necessary if (StringUtil.isNotEmpty(resourceName) && config.isHttpMethodSpecify()) { - resourceName = request.getMethod().toUpperCase() + COLON + resourceName; + resourceName = request.getMethod().toUpperCase() + ":" + resourceName; } return resourceName; } diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelTotalInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebTotalInterceptor.java similarity index 57% rename from sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelTotalInterceptor.java rename to sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebTotalInterceptor.java index 5cda2aff..d356ca36 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelTotalInterceptor.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebTotalInterceptor.java @@ -16,37 +16,31 @@ package com.alibaba.csp.sentinel.adapter.spring.webmvc; import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcTotalConfig; -import com.alibaba.csp.sentinel.log.RecordLog; import javax.servlet.http.HttpServletRequest; /** - * Spring mvc interceptor for all requests. + * 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 SentinelTotalInterceptor extends AbstractSentinelInterceptor { - private SentinelWebMvcTotalConfig config; +public class SentinelWebTotalInterceptor extends AbstractSentinelInterceptor { - public SentinelTotalInterceptor(SentinelWebMvcTotalConfig config) { - super(); - setConfig(config); - setBaseWebMvcConfig(config); - } + private final SentinelWebMvcTotalConfig config; - public SentinelTotalInterceptor() { - this(new SentinelWebMvcTotalConfig()); - } - - public SentinelTotalInterceptor setConfig(SentinelWebMvcTotalConfig config) { + public SentinelWebTotalInterceptor(SentinelWebMvcTotalConfig config) { + super(config); if (config == null) { this.config = new SentinelWebMvcTotalConfig(); - RecordLog.info("Config is null, use default config"); } else { this.config = config; } - RecordLog.info(String.format("SentinelInterceptor config: requestAttributeName=%s, originParser=%s, blockExceptionHandler=%s, totalResourceName=%s", config.getRequestAttributeName(), config.getOriginParser(), config.getBlockExceptionHandler(), config.getTotalResourceName())); - return this; + } + + public SentinelWebTotalInterceptor() { + this(new SentinelWebMvcTotalConfig()); } @Override diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java index fcf61809..b40a06eb 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java @@ -21,19 +21,19 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** - * Handle BlockException + * Handler for the blocked request. * * @author kaizi2009 */ public interface BlockExceptionHandler { /** - * Handle BlockException + * Handle the request when blocked. * - * @param request - * @param response - * @param e Depending on your situation, you can choose to process or throw BlockException - * @throws Exception + * @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-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java index b1df0067..acb74d0e 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java @@ -23,7 +23,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; /** - * Default `BlockException` handler + * Default handler for the blocked request. * * @author kaizi2009 */ @@ -31,6 +31,9 @@ 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); + StringBuffer url = request.getRequestURL(); if ("GET".equals(request.getMethod()) && StringUtil.isNotBlank(request.getQueryString())) { diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java index 9b7f7f89..8541d097 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java @@ -16,17 +16,17 @@ package com.alibaba.csp.sentinel.adapter.spring.webmvc.callback; /** - * Clean sentinel target + * Unify the resource target. * * @author kaizi2009 */ public interface UrlCleaner { /** - * Clean sentinel target + * Unify the resource target. * - * @param originUrl - * @return + * @param originUrl the original URL + * @return the unified resource name */ String clean(String originUrl); } diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java index ef1da39d..03ff0ebc 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java @@ -17,13 +17,16 @@ 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; +import com.alibaba.csp.sentinel.util.AssertUtil; /** - * Common config + * Common base configuration for Spring Web MVC adapter. * * @author kaizi2009 + * @since 1.7.1 */ public abstract class BaseWebMvcConfig { + protected String requestAttributeName; protected BlockExceptionHandler blockExceptionHandler; protected RequestOriginParser originParser; @@ -51,5 +54,4 @@ public abstract class BaseWebMvcConfig { public void setOriginParser(RequestOriginParser originParser) { this.originParser = originParser; } - } diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java index 35f75533..29a2891c 100755 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java @@ -19,12 +19,20 @@ 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_mvc_entry_container"; + 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; - protected boolean httpMethodSpecify; + /** + * Specify whether the URL resource name should contain the HTTP method prefix (e.g. {@code POST:}). + */ + private boolean httpMethodSpecify; public SentinelWebMvcConfig() { super(); @@ -35,15 +43,28 @@ public class SentinelWebMvcConfig extends BaseWebMvcConfig { return urlCleaner; } - public void setUrlCleaner(UrlCleaner urlCleaner) { + public SentinelWebMvcConfig setUrlCleaner(UrlCleaner urlCleaner) { this.urlCleaner = urlCleaner; + return this; } public boolean isHttpMethodSpecify() { return httpMethodSpecify; } - public void setHttpMethodSpecify(boolean httpMethodSpecify) { + public SentinelWebMvcConfig setHttpMethodSpecify(boolean httpMethodSpecify) { this.httpMethodSpecify = httpMethodSpecify; + return this; + } + + @Override + public String toString() { + return "SentinelWebMvcConfig{" + + "urlCleaner=" + urlCleaner + + ", httpMethodSpecify=" + httpMethodSpecify + + ", requestAttributeName='" + requestAttributeName + '\'' + + ", blockExceptionHandler=" + blockExceptionHandler + + ", originParser=" + originParser + + '}'; } } diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java index bf90b2f8..dc8b4af0 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java @@ -15,13 +15,14 @@ */ 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_mvc_total_entry_container"; + public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "$$sentinel_spring_web_total_entry_attr"; private String totalResourceName = DEFAULT_TOTAL_RESOURCE_NAME; @@ -34,14 +35,18 @@ public class SentinelWebMvcTotalConfig extends BaseWebMvcConfig { return totalResourceName; } - /** - * Config total resource name - * - * @param totalResourceName - * @return - */ - public void setTotalResourceName(String 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-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java similarity index 99% rename from sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestInterceptor.java rename to sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java index c7304da8..f6a4b30f 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestInterceptor.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java @@ -1,4 +1,3 @@ -package com.alibaba.csp.sentinel.adapter.spring.webmvc; /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * @@ -14,6 +13,7 @@ package com.alibaba.csp.sentinel.adapter.spring.webmvc; * 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; @@ -46,7 +46,7 @@ import org.springframework.test.web.servlet.MockMvc; @RunWith(SpringRunner.class) @SpringBootTest(classes = TestApplication.class) @AutoConfigureMockMvc -public class TestInterceptor { +public class SentinelSpringMvcIntegrationTest { private static final String HELLO_STR = "Hello!"; @Autowired diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptorTest.java b/sentinel-adapter/sentinel-spring-webmvc-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-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-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java index d6135ead..1ff24fd2 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java @@ -15,8 +15,8 @@ */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.config; -import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelInterceptor; -import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelTotalInterceptor; +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; @@ -74,7 +74,7 @@ public class InterceptorConfig implements WebMvcConfigurer { }); //Add sentinel interceptor - registry.addInterceptor(new SentinelInterceptor(config)).addPathPatterns("/**"); + registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); } private void addSpringMvcTotalInterceptor(InterceptorRegistry registry) { @@ -86,6 +86,6 @@ public class InterceptorConfig implements WebMvcConfigurer { config.setTotalResourceName("my_spring_mvc_total_url_request"); //Add sentinel interceptor - registry.addInterceptor(new SentinelTotalInterceptor(config)).addPathPatterns("/**"); + registry.addInterceptor(new SentinelWebTotalInterceptor(config)).addPathPatterns("/**"); } } diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/pom.xml b/sentinel-demo/sentinel-demo-spring-webmvc/pom.xml index 054649d5..f0825607 100644 --- a/sentinel-demo/sentinel-demo-spring-webmvc/pom.xml +++ b/sentinel-demo/sentinel-demo-spring-webmvc/pom.xml @@ -1,44 +1,44 @@ - - sentinel-demo - com.alibaba.csp - 1.7.0-SNAPSHOT - - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + sentinel-demo + com.alibaba.csp + 1.7.1-SNAPSHOT + + 4.0.0 - sentinel-demo-spring-webmvc + sentinel-demo-spring-webmvc - - 2.1.3.RELEASE - + + 2.1.3.RELEASE + - - - com.alibaba.csp - sentinel-core - - - com.alibaba.csp - sentinel-transport-simple-http - - - com.alibaba.csp - sentinel-spring-webmvc-adapter - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - ${spring.boot.version} - - - org.springframework.boot - spring-boot-starter-test - ${spring.boot.version} - - + + + com.alibaba.csp + sentinel-core + + + com.alibaba.csp + sentinel-transport-simple-http + + + com.alibaba.csp + sentinel-spring-webmvc-adapter + ${project.version} + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + + \ No newline at end of file diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/WebMvcDemoApplication.java b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/WebMvcDemoApplication.java index 4004e562..2d198f9f 100644 --- a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/WebMvcDemoApplication.java +++ b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/WebMvcDemoApplication.java @@ -19,10 +19,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** + *

Add the JVM parameter to connect to the dashboard:

+ * {@code -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-spring-webmvc} + * * @author kaizi2009 - * - * -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-spring-webmvc - * */ @SpringBootApplication public class WebMvcDemoApplication { diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/InterceptorConfig.java b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/InterceptorConfig.java index 308e28fc..58509df2 100644 --- a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/InterceptorConfig.java +++ b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/InterceptorConfig.java @@ -15,10 +15,12 @@ */ package com.alibaba.csp.sentinel.demo.spring.webmvc.config; -import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelInterceptor; -import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelTotalInterceptor; +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.DefaultBlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig; import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcTotalConfig; + import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -33,34 +35,28 @@ public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { - //Add sentinel interceptor + // 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((request, response, e) -> { - //Depending on your situation, you can choose to process or throw - boolean needThrow = true; - if (needThrow) { - throw e; - } else { - //Write string or json string; - response.getWriter().write("Blocked by sentinel"); - } - }); + // Depending on your situation, you can choose to process the BlockException via + // the BlockExceptionHandler or throw it directly, then handle it + // in Spring web global exception handler. - //Custom configuration if necessary + // config.setBlockExceptionHandler((request, response, e) -> { throw e; }); + + // Use the default handler. + config.setBlockExceptionHandler(new DefaultBlockExceptionHandler()); + + // Custom configuration if necessary config.setHttpMethodSpecify(true); config.setOriginParser(request -> request.getHeader("S-user")); - //Add sentinel interceptor - registry.addInterceptor(new SentinelInterceptor(config)).addPathPatterns("/**"); + // Add sentinel interceptor + registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); } private void addSpringMvcTotalInterceptor(InterceptorRegistry registry) { @@ -72,6 +68,6 @@ public class InterceptorConfig implements WebMvcConfigurer { config.setTotalResourceName("my-spring-mvc-total-url-request"); //Add sentinel interceptor - registry.addInterceptor(new SentinelTotalInterceptor(config)).addPathPatterns("/**"); + registry.addInterceptor(new SentinelWebTotalInterceptor(config)).addPathPatterns("/**"); } } diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java index 8dc3d258..decaba9b 100644 --- a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java +++ b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java @@ -16,7 +16,6 @@ package com.alibaba.csp.sentinel.demo.spring.webmvc.config; import com.alibaba.csp.sentinel.demo.spring.webmvc.vo.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; @@ -26,20 +25,23 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** - * Config blocked handler + * Spring configuration for global exception handler. + * This will be activated when the {@code BlockExceptionHandler} + * throws {@link BlockException directly}. + * * @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 + logger.warn("Blocked by Sentinel: {}", e.getRule()); + // Return the customized result. return ResultWrapper.blocked(); } }