Add Sentinel Spring Web MVC adapter module (#1104)
- Add sentinel-spring-webmvc-adapter module and demo
This commit is contained in:
parent
c70565167f
commit
b14534fb35
|
|
@ -24,6 +24,7 @@
|
|||
<module>sentinel-spring-webflux-adapter</module>
|
||||
<module>sentinel-api-gateway-adapter-common</module>
|
||||
<module>sentinel-spring-cloud-gateway-adapter</module>
|
||||
<module>sentinel-spring-webmvc-adapter</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
# Sentinel Spring MVC Interceptor
|
||||
|
||||
Sentinel provides Spring MVC Interceptor integration to enable flow control for web requests, And support url like '/foo/{id}'
|
||||
|
||||
Add the following dependency in `pom.xml` (if you are using Maven):
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-spring-webmvc-adapter</artifactId>
|
||||
<version>x.y.z</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Configure interceptor
|
||||
|
||||
```java
|
||||
@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) {
|
||||
//Configure
|
||||
SentinelWebMvcConfig config = new SentinelWebMvcConfig();
|
||||
//Custom configuration if necessary
|
||||
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("/**");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Configure 'BlockException' handler, there are three options:
|
||||
1. Global exception handling in spring MVC. <Recommend>
|
||||
```java
|
||||
@ControllerAdvice
|
||||
@Order(0)
|
||||
public class SentinelSpringMvcBlockHandlerConfig {
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
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
|
||||
if ("/hello".equals(resourceName)) {
|
||||
//Do something ......
|
||||
//Write string or error page;
|
||||
response.getWriter().write("Blocked by sentinel");
|
||||
} else {
|
||||
//Handle it in global exception handling
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
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` |
|
||||
|
||||
- `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` |
|
||||
|
||||
|
||||
`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 |
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?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-adapter</artifactId>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<version>1.7.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>sentinel-spring-webmvc-adapter</artifactId>
|
||||
|
||||
<properties>
|
||||
<spring.version>5.1.8.RELEASE</spring.version>
|
||||
<spring.boot.version>2.1.3.RELEASE</spring.boot.version>
|
||||
<servlet.api.version>3.1.0</servlet.api.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>${servlet.api.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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 javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
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.StringUtil;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* @author kaizi2009
|
||||
*/
|
||||
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {
|
||||
|
||||
public static final String SPRING_MVC_CONTEXT_NAME = "spring_mvc_context";
|
||||
private static final String EMPTY_ORIGIN = "";
|
||||
protected static final String COLON = ":";
|
||||
private BaseWebMvcConfig baseWebMvcConfig;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
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);
|
||||
|
||||
setEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName(), entry);
|
||||
}
|
||||
return true;
|
||||
} catch (BlockException e) {
|
||||
handleBlockException(request, response, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sentinel resource name.
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
protected abstract String getResourceName(HttpServletRequest request);
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler, Exception ex) throws Exception {
|
||||
Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName());
|
||||
if (entry != null) {
|
||||
traceExceptionAndExit(entry, ex);
|
||||
removeEntryInRequest(request);
|
||||
}
|
||||
ContextUtil.exit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
ModelAndView modelAndView) throws Exception {
|
||||
}
|
||||
|
||||
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));
|
||||
} else {
|
||||
request.setAttribute(name, entry);
|
||||
}
|
||||
}
|
||||
|
||||
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, handle it in spring mvc
|
||||
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;
|
||||
}
|
||||
|
||||
protected void setBaseWebMvcConfig(BaseWebMvcConfig config) {
|
||||
this.baseWebMvcConfig = config;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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 com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* Spring mvc interceptor that integrates with sentinel.
|
||||
*
|
||||
* @author kaizi2009
|
||||
*/
|
||||
public class SentinelInterceptor extends AbstractSentinelInterceptor {
|
||||
private SentinelWebMvcConfig config;
|
||||
|
||||
public SentinelInterceptor(SentinelWebMvcConfig config) {
|
||||
super();
|
||||
setConfig(config);
|
||||
super.setBaseWebMvcConfig(config);
|
||||
}
|
||||
|
||||
public SentinelInterceptor() {
|
||||
this(new SentinelWebMvcConfig());
|
||||
}
|
||||
|
||||
public SentinelInterceptor setConfig(SentinelWebMvcConfig config) {
|
||||
if (config == null) {
|
||||
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) {
|
||||
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() + COLON + resourceName;
|
||||
}
|
||||
return resourceName;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Spring mvc interceptor for all requests.
|
||||
*
|
||||
* @author kaizi2009
|
||||
*/
|
||||
public class SentinelTotalInterceptor extends AbstractSentinelInterceptor {
|
||||
private SentinelWebMvcTotalConfig config;
|
||||
|
||||
public SentinelTotalInterceptor(SentinelWebMvcTotalConfig config) {
|
||||
super();
|
||||
setConfig(config);
|
||||
setBaseWebMvcConfig(config);
|
||||
}
|
||||
|
||||
public SentinelTotalInterceptor() {
|
||||
this(new SentinelWebMvcTotalConfig());
|
||||
}
|
||||
|
||||
public SentinelTotalInterceptor setConfig(SentinelWebMvcTotalConfig 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getResourceName(HttpServletRequest request) {
|
||||
return config.getTotalResourceName();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Handle BlockException
|
||||
*
|
||||
* @author kaizi2009
|
||||
*/
|
||||
public interface BlockExceptionHandler {
|
||||
|
||||
/**
|
||||
* Handle BlockException
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param e Depending on your situation, you can choose to process or throw BlockException
|
||||
* @throws Exception
|
||||
*/
|
||||
void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* Default `BlockException` handler
|
||||
*
|
||||
* @author kaizi2009
|
||||
*/
|
||||
public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
|
||||
StringBuffer url = request.getRequestURL();
|
||||
|
||||
if ("GET".equals(request.getMethod()) && StringUtil.isNotBlank(request.getQueryString())) {
|
||||
url.append("?").append(request.getQueryString());
|
||||
}
|
||||
|
||||
PrintWriter out = response.getWriter();
|
||||
out.print("Blocked by Sentinel (flow limiting)");
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 javax.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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* Clean sentinel target
|
||||
*
|
||||
* @author kaizi2009
|
||||
*/
|
||||
public interface UrlCleaner {
|
||||
|
||||
/**
|
||||
* Clean sentinel target
|
||||
*
|
||||
* @param originUrl
|
||||
* @return
|
||||
*/
|
||||
String clean(String originUrl);
|
||||
}
|
||||
|
|
@ -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
|
||||
*
|
||||
* 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 config
|
||||
*
|
||||
* @author kaizi2009
|
||||
*/
|
||||
public abstract class BaseWebMvcConfig {
|
||||
protected String requestAttributeName;
|
||||
protected BlockExceptionHandler blockExceptionHandler;
|
||||
protected RequestOriginParser originParser;
|
||||
|
||||
public String getRequestAttributeName() {
|
||||
return requestAttributeName;
|
||||
}
|
||||
|
||||
public void setRequestAttributeName(String requestAttributeName) {
|
||||
this.requestAttributeName = requestAttributeName;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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
|
||||
*/
|
||||
public class SentinelWebMvcConfig extends BaseWebMvcConfig {
|
||||
|
||||
public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "sentinel_spring_mvc_entry_container";
|
||||
private UrlCleaner urlCleaner;
|
||||
protected boolean httpMethodSpecify;
|
||||
|
||||
public SentinelWebMvcConfig() {
|
||||
super();
|
||||
setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
public UrlCleaner getUrlCleaner() {
|
||||
return urlCleaner;
|
||||
}
|
||||
|
||||
public void setUrlCleaner(UrlCleaner urlCleaner) {
|
||||
this.urlCleaner = urlCleaner;
|
||||
}
|
||||
|
||||
public boolean isHttpMethodSpecify() {
|
||||
return httpMethodSpecify;
|
||||
}
|
||||
|
||||
public void setHttpMethodSpecify(boolean httpMethodSpecify) {
|
||||
this.httpMethodSpecify = httpMethodSpecify;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.csp.sentinel.adapter.spring.webmvc.config;
|
||||
|
||||
|
||||
/**
|
||||
* @author kaizi2009
|
||||
*/
|
||||
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";
|
||||
|
||||
private String totalResourceName = DEFAULT_TOTAL_RESOURCE_NAME;
|
||||
|
||||
public SentinelWebMvcTotalConfig() {
|
||||
super();
|
||||
setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
public String getTotalResourceName() {
|
||||
return totalResourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Config total resource name
|
||||
*
|
||||
* @param totalResourceName
|
||||
* @return
|
||||
*/
|
||||
public void setTotalResourceName(String totalResourceName) {
|
||||
this.totalResourceName = totalResourceName;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
package com.alibaba.csp.sentinel.adapter.spring.webmvc;
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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 TestInterceptor {
|
||||
|
||||
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.mvc.perform(get("/foo/1").accept(MediaType.TEXT_PLAIN).header(headerName, "userB"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string("foo 1"));
|
||||
|
||||
// This will be blocked and reponse json.
|
||||
this.mvc.perform(
|
||||
get("/foo/2").accept(MediaType.APPLICATION_JSON).header(headerName, limitOrigin))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().json(ResultWrapper.blocked().toJsonString()));
|
||||
this.mvc.perform(get("/foo/3").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(ResultWrapper.blocked().toJsonString()));
|
||||
|
||||
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 reponse 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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.SentinelInterceptor;
|
||||
import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelTotalInterceptor;
|
||||
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 javax.servlet.http.HttpServletRequest;
|
||||
import javax.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.setOriginParser(new RequestOriginParser() {
|
||||
@Override
|
||||
public String parseOrigin(HttpServletRequest request) {
|
||||
return 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("/**");
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
<module>sentinel-demo-spring-cloud-gateway</module>
|
||||
<module>sentinel-demo-zuul-gateway</module>
|
||||
<module>sentinel-demo-etcd-datasource</module>
|
||||
<module>sentinel-demo-spring-webmvc</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
|
|
|||
|
|
@ -0,0 +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>
|
||||
|
||||
<artifactId>sentinel-demo-spring-webmvc</artifactId>
|
||||
|
||||
<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>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* 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.demo.spring.webmvc;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author kaizi2009
|
||||
* <code>
|
||||
* -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-spring-webmvc
|
||||
* </code>
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class WebMvcDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(WebMvcDemoApplication.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.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.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;
|
||||
|
||||
/**
|
||||
* 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((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");
|
||||
}
|
||||
});
|
||||
|
||||
//Custom configuration if necessary
|
||||
config.setHttpMethodSpecify(true);
|
||||
config.setOriginParser(request -> request.getHeader("S-user"));
|
||||
|
||||
//Add sentinel interceptor
|
||||
registry.addInterceptor(new SentinelInterceptor(config)).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
private void addSpringMvcTotalInterceptor(InterceptorRegistry registry) {
|
||||
//Config
|
||||
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("/**");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.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;
|
||||
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 blocked handler
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.demo.spring.webmvc.controller;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Test controller
|
||||
* @author kaizi2009
|
||||
*/
|
||||
@RestController
|
||||
public class WebMvcTestController {
|
||||
|
||||
@GetMapping("/hello")
|
||||
public String apiHello() {
|
||||
doBusiness();
|
||||
return "Hello!";
|
||||
}
|
||||
|
||||
@GetMapping("/err")
|
||||
public String apiError() {
|
||||
doBusiness();
|
||||
return "Oops...";
|
||||
}
|
||||
|
||||
@GetMapping("/foo/{id}")
|
||||
public String apiFoo(@PathVariable("id") Long id) {
|
||||
doBusiness();
|
||||
return "Hello " + id;
|
||||
}
|
||||
|
||||
@GetMapping("/exclude/{id}")
|
||||
public String apiExclude(@PathVariable("id") Long id) {
|
||||
doBusiness();
|
||||
return "Exclude " + id;
|
||||
}
|
||||
|
||||
private void doBusiness() {
|
||||
Random random = new Random(1);
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(random.nextInt(100));
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.demo.spring.webmvc.vo;
|
||||
|
||||
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 String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static ResultWrapper blocked() {
|
||||
return new ResultWrapper(-1, "Blocked by Sentinel");
|
||||
}
|
||||
|
||||
public String toJsonString() {
|
||||
return JSONObject.toJSONString(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
server.port=10000
|
||||
Loading…
Reference in New Issue