diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml b/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml
index 001445ec3e..673da94d80 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml
@@ -23,6 +23,10 @@
com.alibaba.csp
sentinel-core
+
+ com.alibaba.csp
+ sentinel-web-adapter-common
+
javax.servlet
javax.servlet-api
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java
index 0ad80095af..ba4ed67d1c 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java
@@ -15,8 +15,8 @@
*/
package com.alibaba.csp.sentinel.adapter.spring.webmvc.callback;
+import com.alibaba.csp.sentinel.adapter.web.common.DefaultBlockExceptionResponse;
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;
@@ -31,11 +31,10 @@ public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
- // Return 429 (Too Many Requests) by default.
- response.setStatus(429);
-
+ DefaultBlockExceptionResponse expRes = DefaultBlockExceptionResponse.resolve(e.getClass());
+ response.setStatus(expRes.getStatus());
PrintWriter out = response.getWriter();
- out.print("Blocked by Sentinel (flow limiting)");
+ out.print(expRes.getMsg());
out.flush();
out.close();
}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelDefaultBlockExceptionHandlerTest.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelDefaultBlockExceptionHandlerTest.java
new file mode 100644
index 0000000000..85972d9d8c
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelDefaultBlockExceptionHandlerTest.java
@@ -0,0 +1,158 @@
+package com.alibaba.csp.sentinel.adapter.spring.webmvc;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.DefaultInterceptorConfig;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.InterceptorConfig;
+import com.alibaba.csp.sentinel.adapter.web.common.DefaultBlockExceptionResponse;
+import com.alibaba.csp.sentinel.node.ClusterNode;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
+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 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.WebMvcTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.util.Collections;
+
+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;
+
+/**
+ * @author Lingzhi
+ */
+@RunWith(SpringRunner.class)
+@Import(DefaultInterceptorConfig.class)
+@WebMvcTest(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = InterceptorConfig.class))
+public class SentinelDefaultBlockExceptionHandlerTest {
+ @Autowired
+ private MockMvc mvc;
+
+ @Test
+ public void testOriginParser() throws Exception {
+ String springMvcPathVariableUrl = "/foo/{id}";
+ String limitOrigin = "userA";
+ final String headerName = "S-User";
+ configureRulesFor(springMvcPathVariableUrl, 0, limitOrigin);
+
+ // This will be passed since the caller is different: userB
+ 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 since the caller is same: userA
+ DefaultBlockExceptionResponse res = DefaultBlockExceptionResponse.FLOW_EXCEPTION;
+ this.mvc.perform(
+ get("/foo/2").accept(MediaType.APPLICATION_JSON).header(headerName, limitOrigin))
+ .andExpect(status().is(res.getStatus()))
+ .andExpect(content().string(res.getMsg()));
+
+ // This will be passed since the caller is different: ""
+ this.mvc.perform(get("/foo/3").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().string("foo 3"));
+ }
+
+ @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 response json.
+ DefaultBlockExceptionResponse res = DefaultBlockExceptionResponse.resolve(FlowException.class);
+ this.mvc.perform(get(url))
+ .andExpect(status().is(res.getStatus()))
+ .andExpect(content().string(res.getMsg()));
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
+ assertNotNull(cn);
+ assertEquals(repeat, cn.passQps(), 0.01);
+ assertEquals(1, cn.blockRequest(), 1);
+ }
+
+
+ @Test
+ public void testExceptionPerception() throws Exception {
+ String url = "/bizException";
+ configureExceptionDegradeRulesFor(url, 2.6, null);
+ int repeat = 3;
+ for (int i = 0; i < repeat; i++) {
+ this.mvc.perform(get(url))
+ .andExpect(status().isOk())
+ .andExpect(content().string(new ResultWrapper(-1, "Biz error").toJsonString()));
+
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
+ assertNotNull(cn);
+ assertEquals(i + 1, cn.passQps(), 0.01);
+ }
+
+ // This will be blocked and response.
+ DefaultBlockExceptionResponse res = DefaultBlockExceptionResponse.resolve(DegradeException.class);
+ this.mvc.perform(get(url))
+ .andExpect(status().is(res.getStatus()))
+ .andExpect(content().string(res.getMsg()));
+ 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));
+ }
+
+ private void configureExceptionDegradeRulesFor(String resource, double count, String limitApp) {
+ DegradeRule rule = new DegradeRule().setCount(count)
+ .setStatIntervalMs(1000).setMinRequestAmount(1)
+ .setTimeWindow(5).setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
+ rule.setResource(resource);
+ if (StringUtil.isNotBlank(limitApp)) {
+ rule.setLimitApp(limitApp);
+ }
+ DegradeRuleManager.loadRules(Collections.singletonList(rule));
+ }
+
+ @After
+ public void cleanUp() {
+ FlowRuleManager.loadRules(null);
+ DegradeRuleManager.loadRules(null);
+ ClusterBuilderSlot.resetClusterNodes();
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java
index 7f8bbc051e..4579f4ba9b 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java
@@ -15,13 +15,6 @@
*/
package com.alibaba.csp.sentinel.adapter.spring.webmvc;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-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.context.ContextUtil;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
@@ -31,9 +24,6 @@
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;
@@ -44,6 +34,13 @@
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+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;
+
/**
* @author kaizi2009
*/
@@ -94,7 +91,7 @@ public void testOriginParser() throws Exception {
// This will be blocked since the caller is same: userA
this.mvc.perform(
- get("/foo/2").accept(MediaType.APPLICATION_JSON).header(headerName, limitOrigin))
+ get("/foo/2").accept(MediaType.APPLICATION_JSON).header(headerName, limitOrigin))
.andExpect(status().isOk())
.andExpect(content().json(ResultWrapper.blocked().toJsonString()));
@@ -102,8 +99,6 @@ public void testOriginParser() throws Exception {
this.mvc.perform(get("/foo/3").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string("foo 3"));
-
- FlowRuleManager.loadRules(null);
}
@Test
@@ -209,6 +204,7 @@ private void configureExceptionDegradeRulesFor(String resource, double count, St
@After
public void cleanUp() {
FlowRuleManager.loadRules(null);
+ DegradeRuleManager.loadRules(null);
ClusterBuilderSlot.resetClusterNodes();
}
}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/DefaultInterceptorConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/DefaultInterceptorConfig.java
new file mode 100644
index 0000000000..5e81c52d9c
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/DefaultInterceptorConfig.java
@@ -0,0 +1,60 @@
+package com.alibaba.csp.sentinel.adapter.spring.webmvc.config;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelExceptionAware;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebTotalInterceptor;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.DefaultBlockExceptionHandler;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Interceptor Config using DefaultBlockExceptionHandler
+ *
+ * @author Lingzhi
+ */
+@TestConfiguration
+public class DefaultInterceptorConfig implements WebMvcConfigurer {
+
+ @Bean
+ public SentinelExceptionAware sentinelExceptionAware() {
+ return new SentinelExceptionAware();
+ }
+
+ @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 DefaultBlockExceptionHandler());
+
+ //Custom configuration if necessary
+ config.setHttpMethodSpecify(false);
+ config.setWebContextUnify(true);
+ config.setOriginParser(request -> request.getHeader("S-user"));
+
+ //Add sentinel interceptor
+ registry.addInterceptor(new SentinelWebInterceptor(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 SentinelWebTotalInterceptor(config)).addPathPatterns("/**");
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/AbstractSentinelInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/AbstractSentinelInterceptor.java
index dc2e273add..543a2ae871 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/AbstractSentinelInterceptor.java
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/AbstractSentinelInterceptor.java
@@ -15,11 +15,7 @@
*/
package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x;
-import com.alibaba.csp.sentinel.Entry;
-import com.alibaba.csp.sentinel.EntryType;
-import com.alibaba.csp.sentinel.ResourceTypeConstants;
-import com.alibaba.csp.sentinel.SphU;
-import com.alibaba.csp.sentinel.Tracer;
+import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.BaseWebMvcConfig;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.log.RecordLog;
@@ -28,10 +24,14 @@
import com.alibaba.csp.sentinel.util.StringUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
+import java.util.Objects;
+
/**
* Since request may be reprocessed in flow if any forwarding or including or other action
* happened (see {@link jakarta.servlet.ServletRequest#getDispatcherType()}) we will only
@@ -74,7 +74,7 @@ private Integer increaseReference(HttpServletRequest request, String rcKey, int
if (obj == null) {
// initial
- obj = Integer.valueOf(0);
+ obj = 0;
}
Integer newRc = (Integer) obj + step;
@@ -193,12 +193,21 @@ protected void removeEntryInRequest(HttpServletRequest request) {
}
protected void traceExceptionAndExit(Entry entry, Exception ex) {
- if (entry != null) {
- if (ex != null) {
- Tracer.traceEntry(ex, entry);
- }
- entry.exit();
+ if (entry == null) {
+ return;
+ }
+ HttpServletRequest request = getHttpServletRequest();
+ if (request != null
+ && ex == null
+ && increaseReference(request, this.baseWebMvcConfig.getRequestRefName() + ":" + BaseWebMvcConfig.REQUEST_REF_EXCEPTION_NAME, 1) == 1) {
+ //Each interceptor can only catch exception once
+ ex = (Exception) request.getAttribute(BaseWebMvcConfig.REQUEST_REF_EXCEPTION_NAME);
}
+
+ if (ex != null) {
+ Tracer.traceEntry(ex, entry);
+ }
+ entry.exit();
}
protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, String resourceName,
@@ -228,4 +237,10 @@ protected String parseOrigin(HttpServletRequest request) {
return origin;
}
+ private HttpServletRequest getHttpServletRequest() {
+ ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+
+ return Objects.isNull(servletRequestAttributes) ? null : servletRequestAttributes.getRequest();
+ }
+
}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelExceptionAware.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelExceptionAware.java
new file mode 100644
index 0000000000..276f23698d
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelExceptionAware.java
@@ -0,0 +1,26 @@
+package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.BaseWebMvcConfig;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.ModelAndView;
+
+@Order(-1)
+public class SentinelExceptionAware implements HandlerExceptionResolver {
+ @Override
+ public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+ addExceptionToRequest(request, ex);
+ return null;
+ }
+
+ private void addExceptionToRequest(HttpServletRequest httpServletRequest, Exception exception) {
+ if (BlockException.isBlockException(exception)) {
+ return;
+ }
+ httpServletRequest.setAttribute(BaseWebMvcConfig.REQUEST_REF_EXCEPTION_NAME, exception);
+ }
+}
+
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/DefaultBlockExceptionHandler.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/DefaultBlockExceptionHandler.java
index 5181da74e3..dcd96094b2 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/DefaultBlockExceptionHandler.java
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/DefaultBlockExceptionHandler.java
@@ -15,6 +15,7 @@
*/
package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback;
+import com.alibaba.csp.sentinel.adapter.web.common.DefaultBlockExceptionResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -29,14 +30,14 @@
public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
@Override
- public void handle(HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException ex)
+ public void handle(HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException e)
throws Exception {
- // Return 429 (Too Many Requests) by default.
- response.setStatus(429);
-
+ DefaultBlockExceptionResponse expRes = DefaultBlockExceptionResponse.resolve(e.getClass());
+ response.setStatus(expRes.getStatus());
PrintWriter out = response.getWriter();
- out.print("Blocked by Sentinel (flow limiting)");
+ out.print(expRes.getMsg());
out.flush();
out.close();
}
+
}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/BaseWebMvcConfig.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/BaseWebMvcConfig.java
index 84d64b6a3d..0cb3e60814 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/BaseWebMvcConfig.java
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/BaseWebMvcConfig.java
@@ -25,6 +25,7 @@
* @since 1.8.8
*/
public abstract class BaseWebMvcConfig {
+ public final static String REQUEST_REF_EXCEPTION_NAME = "$$sentinel_spring_web_entry_attr-exception";
protected String requestAttributeName;
protected String requestRefName;
@@ -39,10 +40,10 @@ public void setRequestAttributeName(String requestAttributeName) {
this.requestAttributeName = requestAttributeName;
this.requestRefName = this.requestAttributeName + "-rc";
}
-
+
/**
* Paired with attr name used to track reference count.
- *
+ *
* @return
*/
public String getRequestRefName() {
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelDefaultBlockExceptionHandlerTest.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelDefaultBlockExceptionHandlerTest.java
new file mode 100644
index 0000000000..e9030a59ca
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelDefaultBlockExceptionHandlerTest.java
@@ -0,0 +1,158 @@
+package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.DefaultInterceptorConfig;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.InterceptorConfig;
+import com.alibaba.csp.sentinel.adapter.web.common.DefaultBlockExceptionResponse;
+import com.alibaba.csp.sentinel.node.ClusterNode;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
+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 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.WebMvcTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.util.Collections;
+
+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;
+
+/**
+ * @author Lingzhi
+ */
+@RunWith(SpringRunner.class)
+@Import(DefaultInterceptorConfig.class)
+@WebMvcTest(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = InterceptorConfig.class))
+public class SentinelDefaultBlockExceptionHandlerTest {
+ @Autowired
+ private MockMvc mvc;
+
+ @Test
+ public void testOriginParser() throws Exception {
+ String springMvcPathVariableUrl = "/foo/{id}";
+ String limitOrigin = "userA";
+ final String headerName = "S-User";
+ configureRulesFor(springMvcPathVariableUrl, 0, limitOrigin);
+
+ // This will be passed since the caller is different: userB
+ 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 since the caller is same: userA
+ DefaultBlockExceptionResponse res = DefaultBlockExceptionResponse.FLOW_EXCEPTION;
+ this.mvc.perform(
+ get("/foo/2").accept(MediaType.APPLICATION_JSON).header(headerName, limitOrigin))
+ .andExpect(status().is(res.getStatus()))
+ .andExpect(content().string(res.getMsg()));
+
+ // This will be passed since the caller is different: ""
+ this.mvc.perform(get("/foo/3").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().string("foo 3"));
+ }
+
+ @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 response json.
+ DefaultBlockExceptionResponse res = DefaultBlockExceptionResponse.resolve(FlowException.class);
+ this.mvc.perform(get(url))
+ .andExpect(status().is(res.getStatus()))
+ .andExpect(content().string(res.getMsg()));
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
+ assertNotNull(cn);
+ assertEquals(repeat, cn.passQps(), 0.01);
+ assertEquals(1, cn.blockRequest(), 1);
+ }
+
+
+ @Test
+ public void testExceptionPerception() throws Exception {
+ String url = "/bizException";
+ configureExceptionDegradeRulesFor(url, 2.6, null);
+ int repeat = 3;
+ for (int i = 0; i < repeat; i++) {
+ this.mvc.perform(get(url))
+ .andExpect(status().isOk())
+ .andExpect(content().string(new ResultWrapper(-1, "Biz error").toJsonString()));
+
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
+ assertNotNull(cn);
+ assertEquals(i + 1, cn.passQps(), 0.01);
+ }
+
+ // This will be blocked and response.
+ DefaultBlockExceptionResponse res = DefaultBlockExceptionResponse.resolve(DegradeException.class);
+ this.mvc.perform(get(url))
+ .andExpect(status().is(res.getStatus()))
+ .andExpect(content().string(res.getMsg()));
+ 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));
+ }
+
+ private void configureExceptionDegradeRulesFor(String resource, double count, String limitApp) {
+ DegradeRule rule = new DegradeRule().setCount(count)
+ .setStatIntervalMs(1000).setMinRequestAmount(1)
+ .setTimeWindow(5).setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
+ rule.setResource(resource);
+ if (StringUtil.isNotBlank(limitApp)) {
+ rule.setLimitApp(limitApp);
+ }
+ DegradeRuleManager.loadRules(Collections.singletonList(rule));
+ }
+
+ @After
+ public void cleanUp() {
+ FlowRuleManager.loadRules(null);
+ DegradeRuleManager.loadRules(null);
+ ClusterBuilderSlot.resetClusterNodes();
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelSpringMvcIntegrationTest.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelSpringMvcIntegrationTest.java
index f7e7ac7796..c69a7c5db1 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelSpringMvcIntegrationTest.java
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelSpringMvcIntegrationTest.java
@@ -15,23 +15,15 @@
*/
package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-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.context.ContextUtil;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
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;
@@ -42,6 +34,13 @@
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+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;
+
/**
* @author kaizi2009
*/
@@ -100,8 +99,6 @@ public void testOriginParser() throws Exception {
this.mvc.perform(get("/foo/3").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string("foo 3"));
-
- FlowRuleManager.loadRules(null);
}
@Test
@@ -142,6 +139,31 @@ public void testRuntimeException() throws Exception {
assertEquals(1, cn.blockRequest(), 1);
}
+ @Test
+ public void testExceptionPerception() throws Exception {
+ String url = "/bizException";
+ configureExceptionDegradeRulesFor(url, 2.6, null);
+ int repeat = 3;
+ for (int i = 0; i < repeat; i++) {
+ this.mvc.perform(get(url))
+ .andExpect(status().isOk())
+ .andExpect(content().string(new ResultWrapper(-1, "Biz error").toJsonString()));
+
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
+ assertNotNull(cn);
+ assertEquals(i + 1, cn.passQps(), 0.01);
+ }
+
+ // This will be blocked and response 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)
@@ -164,9 +186,21 @@ private void configureExceptionRulesFor(String resource, int count, String limit
FlowRuleManager.loadRules(Collections.singletonList(rule));
}
+ private void configureExceptionDegradeRulesFor(String resource, double count, String limitApp) {
+ DegradeRule rule = new DegradeRule().setCount(count)
+ .setStatIntervalMs(1000).setMinRequestAmount(1)
+ .setTimeWindow(5).setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
+ rule.setResource(resource);
+ if (StringUtil.isNotBlank(limitApp)) {
+ rule.setLimitApp(limitApp);
+ }
+ DegradeRuleManager.loadRules(Collections.singletonList(rule));
+ }
+
@After
public void cleanUp() {
FlowRuleManager.loadRules(null);
+ DegradeRuleManager.loadRules(null);
ClusterBuilderSlot.resetClusterNodes();
}
}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/DefaultBlockExceptionHandlerTest.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/DefaultBlockExceptionHandlerTest.java
index fca59ffe62..205da171b5 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/DefaultBlockExceptionHandlerTest.java
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/DefaultBlockExceptionHandlerTest.java
@@ -1,6 +1,8 @@
package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback;
+import com.alibaba.csp.sentinel.adapter.web.common.DefaultBlockExceptionResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -20,6 +22,10 @@ public void handle_writeBlockPage() throws Exception {
BlockException ex = new FlowException("msg");
h.handle(req, resp, resourceName, ex);
assertEquals(429, resp.getStatus());
+ BlockException dex = new DegradeException("msg");
+ MockHttpServletResponse dresp = new MockHttpServletResponse();
+ h.handle(req, dresp, resourceName, dex);
+ assertEquals(DefaultBlockExceptionResponse.DEGRADE_EXCEPTION.getStatus(), dresp.getStatus());
}
}
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/DefaultInterceptorConfig.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/DefaultInterceptorConfig.java
new file mode 100644
index 0000000000..87d52b2117
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/DefaultInterceptorConfig.java
@@ -0,0 +1,61 @@
+package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.SentinelExceptionAware;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.SentinelWebInterceptor;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.SentinelWebTotalInterceptor;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.DefaultBlockExceptionHandler;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Interceptor Config using DefaultBlockExceptionHandler
+ *
+ * @author Lingzhi
+ */
+@TestConfiguration
+public class DefaultInterceptorConfig implements WebMvcConfigurer {
+
+ @Bean
+ public SentinelExceptionAware sentinelExceptionAware() {
+ return new SentinelExceptionAware();
+ }
+
+
+ @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 DefaultBlockExceptionHandler());
+
+ //Custom configuration if necessary
+ config.setHttpMethodSpecify(false);
+ config.setWebContextUnify(true);
+ config.setOriginParser(request -> request.getHeader("S-user"));
+
+ //Add sentinel interceptor
+ registry.addInterceptor(new SentinelWebInterceptor(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 SentinelWebTotalInterceptor(config)).addPathPatterns("/**");
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/InterceptorConfig.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/InterceptorConfig.java
index 363fea2e9b..b2ca3b680b 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/InterceptorConfig.java
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/InterceptorConfig.java
@@ -15,18 +15,19 @@
*/
package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.SentinelExceptionAware;
import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.SentinelWebInterceptor;
import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.SentinelWebTotalInterceptor;
import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser;
import com.alibaba.csp.sentinel.slots.block.BlockException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
/**
* Config sentinel interceptor
*
@@ -35,6 +36,11 @@
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
+ @Bean
+ public SentinelExceptionAware sentinelExceptionAware() {
+ return new SentinelExceptionAware();
+ }
+
@Override
public void addInterceptors(InterceptorRegistry registry) {
//Add sentinel interceptor
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/SentinelSpringMvcBlockHandlerConfig.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/SentinelSpringMvcBlockHandlerConfig.java
index 6fab6530a0..40805d273c 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/SentinelSpringMvcBlockHandlerConfig.java
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/SentinelSpringMvcBlockHandlerConfig.java
@@ -16,6 +16,7 @@
package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config;
import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.ResultWrapper;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.exception.BizException;
import com.alibaba.csp.sentinel.slots.block.AbstractRule;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.slf4j.Logger;
@@ -33,7 +34,7 @@
@ControllerAdvice
@Order(0)
public class SentinelSpringMvcBlockHandlerConfig {
- private Logger logger = LoggerFactory.getLogger(this.getClass());
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ExceptionHandler(BlockException.class)
@ResponseBody
@@ -51,4 +52,11 @@ public ResultWrapper exceptionHandler(Exception e) {
logger.error("System error", e.getMessage());
return new ResultWrapper(-1, "System error");
}
+
+ @ExceptionHandler(BizException.class)
+ @ResponseBody
+ public ResultWrapper bizExceptionHandler(BizException e) {
+ logger.error("Biz error", e.getMessage());
+ return new ResultWrapper(-1, "Biz error");
+ }
}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/controller/TestController.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/controller/TestController.java
index cf16bff4db..c7cc556139 100644
--- a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/controller/TestController.java
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/controller/TestController.java
@@ -16,6 +16,7 @@
package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.controller;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.exception.BizException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
@@ -49,6 +50,11 @@ public String runtimeException() {
return "runtimeException";
}
+ @GetMapping("/bizException")
+ public String bizException() {
+ throw new BizException();
+ }
+
@GetMapping("/exclude/{id}")
public String apiExclude(@PathVariable("id") Long id) {
return "Exclude " + id;
diff --git a/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/exception/BizException.java b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/exception/BizException.java
new file mode 100644
index 0000000000..82c0452338
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/exception/BizException.java
@@ -0,0 +1,7 @@
+package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.exception;
+
+/**
+ * @author lemonj
+ */
+public class BizException extends RuntimeException {
+}
diff --git a/sentinel-adapter/sentinel-web-adapter-common/pom.xml b/sentinel-adapter/sentinel-web-adapter-common/pom.xml
index cf7fc0f953..870d72ab51 100644
--- a/sentinel-adapter/sentinel-web-adapter-common/pom.xml
+++ b/sentinel-adapter/sentinel-web-adapter-common/pom.xml
@@ -15,6 +15,14 @@
8
+
+ com.alibaba.csp
+ sentinel-core
+
+
+ com.alibaba.csp
+ sentinel-parameter-flow-control
+
junit
junit
diff --git a/sentinel-adapter/sentinel-web-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/web/common/DefaultBlockExceptionResponse.java b/sentinel-adapter/sentinel-web-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/web/common/DefaultBlockExceptionResponse.java
new file mode 100644
index 0000000000..14d04e15f5
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/web/common/DefaultBlockExceptionResponse.java
@@ -0,0 +1,67 @@
+package com.alibaba.csp.sentinel.adapter.web.common;
+
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
+import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
+import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
+
+/**
+ * Default BlockException Response
+ *
+ * @author Lingzhi
+ */
+public enum DefaultBlockExceptionResponse {
+ //Too Many Request
+ FLOW_EXCEPTION(FlowException.class, 429, "Blocked by Sentinel (flow limiting)"),
+ //Too Many Request
+ PARAM_FLOW_EXCEPTION(ParamFlowException.class, 429, "Blocked by Sentinel (frequent parameter flow limiting)"),
+ //Service Unavailable
+ DEGRADE_EXCEPTION(DegradeException.class, 503, "Blocked by Sentinel (circuit breaker)"),
+ //Forbidden
+ AUTHORITY_EXCEPTION(AuthorityException.class, 403, "Blocked by Sentinel (origin request limiting)"),
+ //Too Many Request
+ SYSTEM_BLOCK_EXCEPTION(SystemBlockException.class, 429, "Blocked by Sentinel (system limiting)"),
+ // Bad Request
+ DEFAULT_BLOCK_EXCEPTION(BlockException.class, 400, "Blocked by Sentinel");
+
+ private final Class extends BlockException> exp;
+
+ private final int status;
+ private final String msg;
+
+
+ private static final DefaultBlockExceptionResponse[] VALUES;
+
+ static {
+ VALUES = values();
+ }
+
+ DefaultBlockExceptionResponse(Class extends BlockException> exp, int status, String msg) {
+ this.exp = exp;
+ this.status = status;
+ this.msg = msg;
+ }
+
+
+ public int getStatus() {
+ return status;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public static DefaultBlockExceptionResponse resolve(Class extends BlockException> exp) {
+ // Use cached VALUES instead of values() to prevent array allocation.
+ for (DefaultBlockExceptionResponse res : VALUES) {
+ if (res.exp == exp) {
+ return res;
+ }
+ }
+ return DEFAULT_BLOCK_EXCEPTION;
+ }
+}
+
+