- Add sentinel-spring-webmvc-adapter module and demomaster
@@ -24,6 +24,7 @@ | |||||
<module>sentinel-spring-webflux-adapter</module> | <module>sentinel-spring-webflux-adapter</module> | ||||
<module>sentinel-api-gateway-adapter-common</module> | <module>sentinel-api-gateway-adapter-common</module> | ||||
<module>sentinel-spring-cloud-gateway-adapter</module> | <module>sentinel-spring-cloud-gateway-adapter</module> | ||||
<module>sentinel-spring-webmvc-adapter</module> | |||||
</modules> | </modules> | ||||
<dependencyManagement> | <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-spring-cloud-gateway</module> | ||||
<module>sentinel-demo-zuul-gateway</module> | <module>sentinel-demo-zuul-gateway</module> | ||||
<module>sentinel-demo-etcd-datasource</module> | <module>sentinel-demo-etcd-datasource</module> | ||||
<module>sentinel-demo-spring-webmvc</module> | |||||
</modules> | </modules> | ||||
<dependencies> | <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 |