- Add sentinel-spring-webmvc-adapter module and demomaster
@@ -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 |