|
@@ -0,0 +1,413 @@
|
|
|
+/*
|
|
|
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
|
|
|
+ *
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * https://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+package com.alibaba.csp.sentinel.adapter.jaxrs;
|
|
|
+
|
|
|
+import com.alibaba.csp.sentinel.Constants;
|
|
|
+import com.alibaba.csp.sentinel.CtSph;
|
|
|
+import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig;
|
|
|
+import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback;
|
|
|
+import com.alibaba.csp.sentinel.context.Context;
|
|
|
+import com.alibaba.csp.sentinel.context.ContextUtil;
|
|
|
+import com.alibaba.csp.sentinel.node.ClusterNode;
|
|
|
+import com.alibaba.csp.sentinel.node.EntranceNode;
|
|
|
+import com.alibaba.csp.sentinel.node.Node;
|
|
|
+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 com.alibaba.csp.sentinel.util.function.Supplier;
|
|
|
+import org.junit.After;
|
|
|
+import org.junit.AfterClass;
|
|
|
+import org.junit.BeforeClass;
|
|
|
+import org.junit.Test;
|
|
|
+import org.springframework.boot.SpringApplication;
|
|
|
+import org.springframework.context.ConfigurableApplicationContext;
|
|
|
+import org.springframework.util.SocketUtils;
|
|
|
+
|
|
|
+import javax.ws.rs.ProcessingException;
|
|
|
+import javax.ws.rs.client.Client;
|
|
|
+import javax.ws.rs.client.ClientBuilder;
|
|
|
+import javax.ws.rs.core.MediaType;
|
|
|
+import javax.ws.rs.core.Response;
|
|
|
+
|
|
|
+import java.lang.reflect.Method;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.concurrent.*;
|
|
|
+
|
|
|
+import static org.junit.Assert.*;
|
|
|
+import static org.junit.Assert.assertNull;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author sea
|
|
|
+ */
|
|
|
+public class ClientFilterTest {
|
|
|
+
|
|
|
+ private static final String HELLO_STR = "Hello!";
|
|
|
+
|
|
|
+ static int port;
|
|
|
+
|
|
|
+ static String host;
|
|
|
+
|
|
|
+ static Client client;
|
|
|
+
|
|
|
+ static ConfigurableApplicationContext ctx;
|
|
|
+
|
|
|
+ @BeforeClass
|
|
|
+ public static void startApplication() {
|
|
|
+ client = ClientBuilder.newBuilder()
|
|
|
+ .connectTimeout(3, TimeUnit.SECONDS)
|
|
|
+ .readTimeout(3, TimeUnit.SECONDS)
|
|
|
+ .build();
|
|
|
+
|
|
|
+ port = SocketUtils.findAvailableTcpPort();
|
|
|
+ host = "http://127.0.0.1:" + port;
|
|
|
+ SpringApplication springApplication = new SpringApplication(TestApplication.class);
|
|
|
+ ctx = springApplication.run("--spring.profiles.active=client", "--server.port=" + port);
|
|
|
+ }
|
|
|
+
|
|
|
+ @AfterClass
|
|
|
+ public static void shutdown() {
|
|
|
+ ctx.close();
|
|
|
+
|
|
|
+ Context context = ContextUtil.getContext();
|
|
|
+ if (context != null) {
|
|
|
+ context.setCurEntry(null);
|
|
|
+ ContextUtil.exit();
|
|
|
+ }
|
|
|
+
|
|
|
+ Constants.ROOT.removeChildList();
|
|
|
+
|
|
|
+ ClusterBuilderSlot.getClusterNodeMap().clear();
|
|
|
+
|
|
|
+ // Clear chainMap in CtSph
|
|
|
+ try {
|
|
|
+ Method resetChainMapMethod = CtSph.class.getDeclaredMethod("resetChainMap");
|
|
|
+ resetChainMapMethod.setAccessible(true);
|
|
|
+ resetChainMapMethod.invoke(null);
|
|
|
+ } catch (Exception e) {
|
|
|
+ // Empty
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @After
|
|
|
+ public void cleanUp() {
|
|
|
+ FlowRuleManager.loadRules(null);
|
|
|
+ ClusterBuilderSlot.resetClusterNodes();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testClientGetHello() {
|
|
|
+ final String url = "/test/hello";
|
|
|
+ String resourceName = "GET:" + url;
|
|
|
+ Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier<Response>() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Response get() {
|
|
|
+ return client.target(host).path(url).request()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ assertEquals(200, response.getStatus());
|
|
|
+ assertEquals(HELLO_STR, response.readEntity(String.class));
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(1, cn.passQps(), 0.01);
|
|
|
+
|
|
|
+ String context = "";
|
|
|
+ for (Node n : Constants.ROOT.getChildList()) {
|
|
|
+ if (n instanceof EntranceNode) {
|
|
|
+ String id = ((EntranceNode) n).getId().getName();
|
|
|
+ if (url.equals(id)) {
|
|
|
+ context = ((EntranceNode) n).getId().getName();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ assertEquals("", context);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testClientAsyncGetHello() throws InterruptedException, ExecutionException {
|
|
|
+ final String url = "/test/async-hello";
|
|
|
+ final String resourceName = "GET:" + url;
|
|
|
+
|
|
|
+ Future<Response> future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier<Future<Response>>() {
|
|
|
+ @Override
|
|
|
+ public Future<Response> get() {
|
|
|
+ return client.target(host).path(url).request()
|
|
|
+ .async()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(HELLO_STR, future.get().readEntity(String.class));
|
|
|
+
|
|
|
+ cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(1, cn.passQps(), 0.01);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testCustomResourceName() {
|
|
|
+ final String url = "/test/hello/{name}";
|
|
|
+ final String resourceName = "GET:" + url;
|
|
|
+
|
|
|
+ Response response1 = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier<Response>() {
|
|
|
+ @Override
|
|
|
+ public Response get() {
|
|
|
+ return client.target(host)
|
|
|
+ .path(url)
|
|
|
+ .resolveTemplate("name", "abc")
|
|
|
+ .request()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ assertEquals(200, response1.getStatus());
|
|
|
+ assertEquals("Hello abc !", response1.readEntity(String.class));
|
|
|
+
|
|
|
+ Response response2 = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier<Response>() {
|
|
|
+ @Override
|
|
|
+ public Response get() {
|
|
|
+ return client.target(host)
|
|
|
+ .path(url)
|
|
|
+ .resolveTemplate("name", "def")
|
|
|
+ .request()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ assertEquals(javax.ws.rs.core.Response.Status.OK.getStatusCode(), response2.getStatus());
|
|
|
+ assertEquals("Hello def !", response2.readEntity(String.class));
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(2, cn.passQps(), 0.01);
|
|
|
+
|
|
|
+ assertNull(ClusterBuilderSlot.getClusterNode("/test/hello/abc"));
|
|
|
+ assertNull(ClusterBuilderSlot.getClusterNode("/test/hello/def"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testClientFallback() {
|
|
|
+ final String url = "/test/hello";
|
|
|
+ final String resourceName = "GET:" + url;
|
|
|
+ configureRulesFor(resourceName, 0);
|
|
|
+
|
|
|
+ Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier<Response>() {
|
|
|
+ @Override
|
|
|
+ public Response get() {
|
|
|
+ return client.target(host).path(url).request()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ assertEquals(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode(), response.getStatus());
|
|
|
+ assertEquals("Blocked by Sentinel (flow limiting)", response.readEntity(String.class));
|
|
|
+
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(0, cn.passQps(), 0.01);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testClientCustomFallback() {
|
|
|
+ final String url = "/test/hello";
|
|
|
+ final String resourceName = "GET:" + url;
|
|
|
+ configureRulesFor(resourceName, 0);
|
|
|
+
|
|
|
+ SentinelJaxRsConfig.setJaxRsFallback(new SentinelJaxRsFallback() {
|
|
|
+ @Override
|
|
|
+ public javax.ws.rs.core.Response fallbackResponse(String route, Throwable cause) {
|
|
|
+ return javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.OK)
|
|
|
+ .entity("Blocked by Sentinel (flow limiting)")
|
|
|
+ .type(MediaType.APPLICATION_JSON_TYPE)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Future<Response> fallbackFutureResponse(final String route, final Throwable cause) {
|
|
|
+ return new FutureTask<>(new Callable<Response>() {
|
|
|
+ @Override
|
|
|
+ public Response call() throws Exception {
|
|
|
+ return fallbackResponse(route, cause);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier<Response>() {
|
|
|
+ @Override
|
|
|
+ public Response get() {
|
|
|
+ return client.target(host).path(url).request()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ assertEquals(javax.ws.rs.core.Response.Status.OK.getStatusCode(), response.getStatus());
|
|
|
+ assertEquals("Blocked by Sentinel (flow limiting)", response.readEntity(String.class));
|
|
|
+
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(0, cn.passQps(), 0.01);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testServerReturn400() {
|
|
|
+ final String url = "/test/400";
|
|
|
+ final String resourceName = "GET:" + url;
|
|
|
+ Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier<Response>() {
|
|
|
+ @Override
|
|
|
+ public Response get() {
|
|
|
+ return client.target(host).path(url).request()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
|
|
+ assertEquals("test return 400", response.readEntity(String.class));
|
|
|
+
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(1, cn.passQps(), 0.01);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testServerReturn500() {
|
|
|
+ final String url = "/test/ex";
|
|
|
+ final String resourceName = "GET:" + url;
|
|
|
+ Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier<Response>() {
|
|
|
+ @Override
|
|
|
+ public Response get() {
|
|
|
+ return client.target(host).path(url).request()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus());
|
|
|
+ assertEquals("test exception mapper", response.readEntity(String.class));
|
|
|
+
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(1, cn.passQps(), 0.01);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testServerTimeout() {
|
|
|
+ final String url = "/test/delay/10";
|
|
|
+ final String resourceName = "GET:/test/delay/{seconds}";
|
|
|
+ try {
|
|
|
+ SentinelJaxRsClientTemplate.execute(resourceName, new Supplier<Response>() {
|
|
|
+ @Override
|
|
|
+ public Response get() {
|
|
|
+ return client.target(host).path(url).request()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (ProcessingException e) {
|
|
|
+ //ignore
|
|
|
+ }
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(0, cn.passQps(), 0.01);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testFutureGetServerTimeout() {
|
|
|
+ final String url = "/test/delay/10";
|
|
|
+ final String resourceName = "GET:/test/delay/{seconds}";
|
|
|
+ try {
|
|
|
+ Future<Response> future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier<Future<Response>>() {
|
|
|
+ @Override
|
|
|
+ public Future<Response> get() {
|
|
|
+ return client.target(host).path(url).request()
|
|
|
+ .async()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ future.get();
|
|
|
+ } catch (Exception e) {
|
|
|
+ //ignore
|
|
|
+ }
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(0, cn.passQps(), 0.01);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testFutureGetTimeout() {
|
|
|
+ final String url = "/test/delay/10";
|
|
|
+ final String resourceName = "GET:/test/delay/{seconds}";
|
|
|
+ try {
|
|
|
+ Future<Response> future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier<Future<Response>>() {
|
|
|
+ @Override
|
|
|
+ public Future<Response> get() {
|
|
|
+ return client.target(host).path(url).request()
|
|
|
+ .async()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ future.get(1, TimeUnit.SECONDS);
|
|
|
+ } catch (Exception e) {
|
|
|
+ //ignore
|
|
|
+ }
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(0, cn.passQps(), 0.01);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testCancelFuture() {
|
|
|
+ final String url = "/test/delay/10";
|
|
|
+ final String resourceName = "GET:/test/delay/{seconds}";
|
|
|
+ try {
|
|
|
+ Future<Response> future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier<Future<Response>>() {
|
|
|
+ @Override
|
|
|
+ public Future<Response> get() {
|
|
|
+ return client.target(host).path(url).request()
|
|
|
+ .async()
|
|
|
+ .get();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ future.cancel(false);
|
|
|
+ } catch (Exception e) {
|
|
|
+ //ignore
|
|
|
+ }
|
|
|
+
|
|
|
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
|
|
|
+ assertNotNull(cn);
|
|
|
+ assertEquals(1, cn.passQps(), 0.01);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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));
|
|
|
+ }
|
|
|
+}
|