From 056a09660f0546fb8328076e0796178c74bad834 Mon Sep 17 00:00:00 2001 From: linzyoo <17732834@qq.com> Date: Sat, 31 Aug 2024 00:19:24 +0800 Subject: [PATCH] 1. fix the issue#3443 for webmvc-v6x-adapter 2. add the Sentinel Exception Aware for webmvc-v6x-adapter. --- ...tinelDefaultBlockExceptionHandlerTest.java | 1 + .../SentinelSpringMvcIntegrationTest.java | 20 +-- .../AbstractSentinelInterceptor.java | 37 ++-- .../webmvc_v6x/SentinelExceptionAware.java | 26 +++ .../DefaultBlockExceptionHandler.java | 11 +- .../webmvc_v6x/config/BaseWebMvcConfig.java | 5 +- ...tinelDefaultBlockExceptionHandlerTest.java | 162 ++++++++++++++++++ .../SentinelSpringMvcIntegrationTest.java | 56 ++++-- .../DefaultBlockExceptionHandlerTest.java | 6 + .../config/DefaultInterceptorConfig.java | 61 +++++++ .../webmvc_v6x/config/InterceptorConfig.java | 12 +- .../SentinelSpringMvcBlockHandlerConfig.java | 10 +- .../webmvc_v6x/controller/TestController.java | 6 + .../webmvc_v6x/exception/BizException.java | 7 + 14 files changed, 377 insertions(+), 43 deletions(-) create mode 100644 sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelExceptionAware.java create mode 100644 sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelDefaultBlockExceptionHandlerTest.java create mode 100644 sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/DefaultInterceptorConfig.java create mode 100644 sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/exception/BizException.java 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 index 3f4df87e1d..96ae246548 100644 --- 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 @@ -154,6 +154,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/SentinelSpringMvcIntegrationTest.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java index 7f8bbc051e..5767c7eec0 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())); @@ -209,6 +206,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-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..beb299c07c --- /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,162 @@ +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")); + + FlowRuleManager.loadRules(null); + } + + @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); + + FlowRuleManager.loadRules(null); + } + + + @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..b500ed4418 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 */ @@ -142,6 +141,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 +188,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 { +}