diff --git a/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/GrpcTestServer.java b/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/GrpcTestServer.java index 48b5f48c..f46c5718 100644 --- a/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/GrpcTestServer.java +++ b/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/GrpcTestServer.java @@ -1,3 +1,18 @@ +/* + * 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.grpc; import io.grpc.Server; @@ -6,18 +21,18 @@ import io.grpc.ServerBuilder; import java.io.IOException; class GrpcTestServer { + private Server server; - GrpcTestServer() { - } + GrpcTestServer() {} - void start(int port, boolean shouldintercept) throws IOException { + void start(int port, boolean shouldIntercept) throws IOException { if (server != null) { throw new IllegalStateException("Server already running!"); } ServerBuilder serverBuild = ServerBuilder.forPort(port) - .addService(new FooServiceImpl()); - if (shouldintercept) { + .addService(new FooServiceImpl()); + if (shouldIntercept) { serverBuild.intercept(new SentinelGrpcServerInterceptor()); } server = serverBuild.build(); diff --git a/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcClientInterceptorTest.java b/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcClientInterceptorTest.java index 7c6972e6..bc1dba73 100755 --- a/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcClientInterceptorTest.java +++ b/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcClientInterceptorTest.java @@ -27,6 +27,8 @@ import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import io.grpc.StatusRuntimeException; +import org.junit.After; +import org.junit.Test; import static org.junit.Assert.*; @@ -41,9 +43,9 @@ public class SentinelGrpcClientInterceptorTest { private final int threshold = 2; private final GrpcTestServer server = new GrpcTestServer(); - private void configureFlowRule() { + private void configureFlowRule(int count) { FlowRule rule = new FlowRule() - .setCount(threshold) + .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS) .setResource(resourceName) .setLimitApp("default") @@ -51,42 +53,44 @@ public class SentinelGrpcClientInterceptorTest { FlowRuleManager.loadRules(Collections.singletonList(rule)); } - //@Test + @Test public void testGrpcClientInterceptor() throws Exception { final int port = 19328; - configureFlowRule(); + configureFlowRule(threshold); server.start(port, false); FooServiceClient client = new FooServiceClient("localhost", port, new SentinelGrpcClientInterceptor()); - final int total = 8; - for (int i = 0; i < total; i++) { - sendRequest(client); - } + + assertTrue(sendRequest(client)); ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(resourceName, EntryType.OUT); assertNotNull(clusterNode); + assertEquals(1, clusterNode.passQps()); - assertEquals((total - threshold) / 2, clusterNode.blockRequest()); - assertEquals(total / 2, clusterNode.totalRequest()); + // Not allowed to pass. + configureFlowRule(0); - long totalQps = clusterNode.totalQps(); - long passQps = clusterNode.passQps(); - long blockQps = clusterNode.blockQps(); - assertEquals(total, totalQps); - assertEquals(total - threshold, blockQps); - assertEquals(threshold, passQps); + // The second request will be blocked. + assertFalse(sendRequest(client)); + assertEquals(1, clusterNode.blockQps()); server.stop(); } - private void sendRequest(FooServiceClient client) { + private boolean sendRequest(FooServiceClient client) { try { FooResponse response = client.sayHello(FooRequest.newBuilder().setName("Sentinel").setId(666).build()); - System.out.println(ClusterBuilderSlot.getClusterNode(resourceName, EntryType.OUT).avgRt()); System.out.println("Response: " + response); + return true; } catch (StatusRuntimeException ex) { System.out.println("Blocked, cause: " + ex.getMessage()); + return false; } } + @After + public void cleanUp() { + FlowRuleManager.loadRules(null); + ClusterBuilderSlot.getClusterNodeMap().clear(); + } } \ No newline at end of file diff --git a/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcServerInterceptorTest.java b/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcServerInterceptorTest.java index 432a6281..da262ea3 100755 --- a/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcServerInterceptorTest.java +++ b/sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcServerInterceptorTest.java @@ -27,6 +27,8 @@ import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import io.grpc.StatusRuntimeException; +import org.junit.After; +import org.junit.Test; import static org.junit.Assert.*; @@ -43,9 +45,9 @@ public class SentinelGrpcServerInterceptorTest { private FooServiceClient client; - private void configureFlowRule() { + private void configureFlowRule(int count) { FlowRule rule = new FlowRule() - .setCount(threshold) + .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS) .setResource(resourceName) .setLimitApp("default") @@ -53,41 +55,44 @@ public class SentinelGrpcServerInterceptorTest { FlowRuleManager.loadRules(Collections.singletonList(rule)); } - //@Test + @Test public void testGrpcServerInterceptor() throws Exception { final int port = 19329; client = new FooServiceClient("localhost", port); - configureFlowRule(); + configureFlowRule(threshold); server.start(port, true); - final int total = 8; - for (int i = 0; i < total; i++) { - sendRequest(); - } + assertTrue(sendRequest()); ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(resourceName, EntryType.IN); assertNotNull(clusterNode); + assertEquals(1, clusterNode.passQps()); - assertEquals((total - threshold) / 2, clusterNode.blockRequest()); - assertEquals(total / 2, clusterNode.totalRequest()); + // Not allowed to pass. + configureFlowRule(0); - long totalQps = clusterNode.totalQps(); - long passQps = clusterNode.passQps(); - long blockQps = clusterNode.blockQps(); - assertEquals(total, totalQps); - assertEquals(total - threshold, blockQps); - assertEquals(threshold, passQps); + // The second request will be blocked. + assertFalse(sendRequest()); + assertEquals(1, clusterNode.blockQps()); server.stop(); } - private void sendRequest() { + private boolean sendRequest() { try { FooResponse response = client.anotherHello(FooRequest.newBuilder().setName("Sentinel").setId(666).build()); System.out.println("Response: " + response); + return true; } catch (StatusRuntimeException ex) { System.out.println("Blocked, cause: " + ex.getMessage()); + return false; } } + + @After + public void cleanUp() { + FlowRuleManager.loadRules(null); + ClusterBuilderSlot.getClusterNodeMap().clear(); + } } \ No newline at end of file diff --git a/sentinel-adapter/sentinel-web-servlet/pom.xml b/sentinel-adapter/sentinel-web-servlet/pom.xml index 335b5137..9e4310a9 100755 --- a/sentinel-adapter/sentinel-web-servlet/pom.xml +++ b/sentinel-adapter/sentinel-web-servlet/pom.xml @@ -28,5 +28,23 @@ ${servlet.api.version} provided + + + junit + junit + test + + + org.springframework.boot + spring-boot-starter-web + 1.5.17.RELEASE + test + + + org.springframework.boot + spring-boot-starter-test + 1.5.17.RELEASE + test + \ No newline at end of file diff --git a/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/WebCallbackManager.java b/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/WebCallbackManager.java index af392e51..73231bcf 100755 --- a/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/WebCallbackManager.java +++ b/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/WebCallbackManager.java @@ -57,8 +57,7 @@ public class WebCallbackManager { return requestOriginParser; } - public static void setRequestOriginParser( - RequestOriginParser requestOriginParser) { + public static void setRequestOriginParser(RequestOriginParser requestOriginParser) { WebCallbackManager.requestOriginParser = requestOriginParser; } } diff --git a/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/util/FilterUtil.java b/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/util/FilterUtil.java index 4dbc1bfc..626aecf1 100755 --- a/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/util/FilterUtil.java +++ b/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/util/FilterUtil.java @@ -64,7 +64,7 @@ public final class FilterUtil { url.append("?").append(request.getQueryString()); } - if (StringUtil.isEmpty(WebServletConfig.getBlockPage())) { + if (StringUtil.isBlank(WebServletConfig.getBlockPage())) { writeDefaultBlockedPage(response); } else { String redirectUrl = WebServletConfig.getBlockPage() + "?http_referer=" + url.toString(); @@ -75,7 +75,7 @@ public final class FilterUtil { private static void writeDefaultBlockedPage(HttpServletResponse response) throws IOException { PrintWriter out = response.getWriter(); - out.println("Blocked by Sentinel (flow limiting)"); + out.print(DEFAULT_BLOCK_MSG); out.flush(); out.close(); } @@ -183,5 +183,7 @@ public final class FilterUtil { return i; } + public static final String DEFAULT_BLOCK_MSG = "Blocked by Sentinel (flow limiting)"; + private FilterUtil() {} } diff --git a/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilterTest.java b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilterTest.java new file mode 100644 index 00000000..2602f17d --- /dev/null +++ b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilterTest.java @@ -0,0 +1,176 @@ +/* + * 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.servlet; + +import java.util.Collections; + +import javax.servlet.http.HttpServletRequest; + +import com.alibaba.csp.sentinel.adapter.servlet.callback.DefaultUrlCleaner; +import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser; +import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner; +import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager; +import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; +import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil; +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 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; + +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * @author Eric Zhao + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestApplication.class) +@AutoConfigureMockMvc +public class CommonFilterTest { + + private static final String HELLO_STR = "Hello!"; + + @Autowired + private MockMvc mvc; + + private void configureRulesFor(String resource, int count) { + configureRulesFor(resource, count, "default"); + } + + 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)); + } + + @Test + public void testCommonFilterMiscellaneous() 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()); + + testCommonBlockAndRedirectBlockPage(url, cn); + + // Test for url cleaner. + testUrlCleaner(); + + testCustomOriginParser(); + } + + private void testCommonBlockAndRedirectBlockPage(String url, ClusterNode cn) throws Exception { + configureRulesFor(url, 0); + // The request will be blocked and response is default block message. + this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN)) + .andExpect(status().isOk()) + .andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG)); + assertEquals(1, cn.blockQps()); + + // Test for redirect. + String redirectUrl = "http://some-location.com"; + WebServletConfig.setBlockPage(redirectUrl); + this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN)) + .andExpect(status().is3xxRedirection()) + .andExpect(header().string("Location", redirectUrl + "?http_referer=http://localhost/hello")); + + FlowRuleManager.loadRules(null); + WebServletConfig.setBlockPage(""); + } + + private void testUrlCleaner() throws Exception { + final String fooPrefix = "/foo/"; + String url1 = fooPrefix + 1; + String url2 = fooPrefix + 2; + WebCallbackManager.setUrlCleaner(new UrlCleaner() { + @Override + public String clean(String originUrl) { + if (originUrl.startsWith(fooPrefix)) { + return "/foo/*"; + } + return originUrl; + } + }); + this.mvc.perform(get(url1).accept(MediaType.TEXT_PLAIN)) + .andExpect(status().isOk()) + .andExpect(content().string("Hello 1")); + this.mvc.perform(get(url2).accept(MediaType.TEXT_PLAIN)) + .andExpect(status().isOk()) + .andExpect(content().string("Hello 2")); + ClusterNode cn = ClusterBuilderSlot.getClusterNode(fooPrefix + "*"); + assertEquals(2, cn.passQps()); + assertNull(ClusterBuilderSlot.getClusterNode(url1)); + assertNull(ClusterBuilderSlot.getClusterNode(url2)); + + WebCallbackManager.setUrlCleaner(new DefaultUrlCleaner()); + } + + private void testCustomOriginParser() throws Exception { + String url = "/hello"; + String limitOrigin = "userA"; + final String headerName = "S-User"; + configureRulesFor(url, 0, limitOrigin); + + WebCallbackManager.setRequestOriginParser(new RequestOriginParser() { + @Override + public String parseOrigin(HttpServletRequest request) { + String origin = request.getHeader(headerName); + return origin != null ? origin : ""; + } + }); + + this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN).header(headerName, "userB")) + .andExpect(status().isOk()) + .andExpect(content().string(HELLO_STR)); + // This will be blocked. + this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN).header(headerName, limitOrigin)) + .andExpect(status().isOk()) + .andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG)); + this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN)) + .andExpect(status().isOk()) + .andExpect(content().string(HELLO_STR)); + + WebCallbackManager.setRequestOriginParser(null); + FlowRuleManager.loadRules(null); + } + + @After + public void cleanUp() { + FlowRuleManager.loadRules(null); + ClusterBuilderSlot.getClusterNodeMap().clear(); + } +} diff --git a/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/FilterConfig.java b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/FilterConfig.java new file mode 100644 index 00000000..119c6b9b --- /dev/null +++ b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/FilterConfig.java @@ -0,0 +1,38 @@ +/* + * 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.servlet; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Eric Zhao + */ +@Configuration +public class FilterConfig { + + @Bean + public FilterRegistrationBean sentinelFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new CommonFilter()); + registration.addUrlPatterns("/*"); + registration.setName("sentinelFilter"); + registration.setOrder(1); + + return registration; + } +} diff --git a/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/TestApplication.java b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/TestApplication.java new file mode 100644 index 00000000..01913205 --- /dev/null +++ b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/TestApplication.java @@ -0,0 +1,30 @@ +/* + * 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.servlet; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Eric Zhao + */ +@SpringBootApplication +public class TestApplication { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } +} diff --git a/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/TestController.java b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/TestController.java new file mode 100644 index 00000000..6221190b --- /dev/null +++ b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/TestController.java @@ -0,0 +1,42 @@ +/* + * 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.servlet; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Eric Zhao + */ +@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 "Hello " + id; + } +}