Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -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): | |||
</dependency> | |||
``` | |||
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. <Recommend> | |||
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"; | |||
} | |||
} | |||
``` | |||
2. Use `DefaultBlockExceptionHandler` | |||
```java | |||
//SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig(); | |||
SentinelWebMvcConfig config = new SentinelWebMvcConfig(); | |||
config.setBlockExceptionHandler(new DefaultBlockExceptionHandler()); | |||
``` | |||
3. `implements BlockExceptionHandler` | |||
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 | |||
//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:<br/>1. The default value is null, you can hanlde `BlockException` in spring MVC;<br/>2.Use `DefaultBlockExceptionHandler`;<br/>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` | |
@@ -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"> | |||
<parent> | |||
<artifactId>sentinel-adapter</artifactId> | |||
<groupId>com.alibaba.csp</groupId> | |||
<version>1.7.0-SNAPSHOT</version> | |||
<artifactId>sentinel-adapter</artifactId> | |||
<version>1.7.1-SNAPSHOT</version> | |||
</parent> | |||
<modelVersion>4.0.0</modelVersion> | |||
<artifactId>sentinel-spring-webmvc-adapter</artifactId> | |||
<packaging>jar</packaging> | |||
<properties> | |||
<spring.version>5.1.8.RELEASE</spring.version> | |||
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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); | |||
} | |||
public SentinelTotalInterceptor() { | |||
this(new SentinelWebMvcTotalConfig()); | |||
} | |||
private final SentinelWebMvcTotalConfig config; | |||
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 |
@@ -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; | |||
@@ -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())) { | |||
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -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 |
@@ -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); | |||
} | |||
} |
@@ -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("/**"); | |||
} | |||
} |
@@ -1,44 +1,44 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<parent> | |||
<artifactId>sentinel-demo</artifactId> | |||
<groupId>com.alibaba.csp</groupId> | |||
<version>1.7.0-SNAPSHOT</version> | |||
</parent> | |||
<modelVersion>4.0.0</modelVersion> | |||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<parent> | |||
<artifactId>sentinel-demo</artifactId> | |||
<groupId>com.alibaba.csp</groupId> | |||
<version>1.7.1-SNAPSHOT</version> | |||
</parent> | |||
<modelVersion>4.0.0</modelVersion> | |||
<artifactId>sentinel-demo-spring-webmvc</artifactId> | |||
<artifactId>sentinel-demo-spring-webmvc</artifactId> | |||
<properties> | |||
<spring.boot.version>2.1.3.RELEASE</spring.boot.version> | |||
</properties> | |||
<properties> | |||
<spring.boot.version>2.1.3.RELEASE</spring.boot.version> | |||
</properties> | |||
<dependencies> | |||
<dependency> | |||
<groupId>com.alibaba.csp</groupId> | |||
<artifactId>sentinel-core</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.alibaba.csp</groupId> | |||
<artifactId>sentinel-transport-simple-http</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.alibaba.csp</groupId> | |||
<artifactId>sentinel-spring-webmvc-adapter</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-web</artifactId> | |||
<version>${spring.boot.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-test</artifactId> | |||
<version>${spring.boot.version}</version> | |||
</dependency> | |||
</dependencies> | |||
<dependencies> | |||
<dependency> | |||
<groupId>com.alibaba.csp</groupId> | |||
<artifactId>sentinel-core</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.alibaba.csp</groupId> | |||
<artifactId>sentinel-transport-simple-http</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.alibaba.csp</groupId> | |||
<artifactId>sentinel-spring-webmvc-adapter</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-web</artifactId> | |||
<version>${spring.boot.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-test</artifactId> | |||
<version>${spring.boot.version}</version> | |||
</dependency> | |||
</dependencies> | |||
</project> |
@@ -19,10 +19,10 @@ import org.springframework.boot.SpringApplication; | |||
import org.springframework.boot.autoconfigure.SpringBootApplication; | |||
/** | |||
* <p>Add the JVM parameter to connect to the dashboard:</p> | |||
* {@code -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-spring-webmvc} | |||
* | |||
* @author kaizi2009 | |||
* <code> | |||
* -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-spring-webmvc | |||
* </code> | |||
*/ | |||
@SpringBootApplication | |||
public class WebMvcDemoApplication { | |||
@@ -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("/**"); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |