get() {
+ return client.target(host).path(url).request()
+ .async()
+ .get();
+ }
+ });
+```
+
+When a request is blocked, Sentinel jax-rs filter will return Response with status of TOO_MANY_REQUESTS indicating the request is rejected.
+
+You can customize it by implement your own `SentinelJaxRsFallback` and register to `SentinelJaxRsConfig`.
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/pom.xml b/sentinel-adapter/sentinel-jax-rs-adapter/pom.xml
new file mode 100755
index 00000000..b0a94298
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/pom.xml
@@ -0,0 +1,74 @@
+
+
+ 4.0.0
+
+
+ com.alibaba.csp
+ sentinel-adapter
+ 1.8.0-SNAPSHOT
+
+
+ sentinel-jax-rs-adapter
+ jar
+
+
+ 2.1.1
+
+
+
+
+ com.alibaba.csp
+ sentinel-core
+
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+ ${javax.ws.rs-api.version}
+ provided
+
+
+
+ junit
+ junit
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ 2.2.6.RELEASE
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 2.2.6.RELEASE
+ test
+
+
+ io.rest-assured
+ rest-assured
+ 4.3.0
+ test
+
+
+ org.jboss.resteasy
+ resteasy-spring-boot-starter
+ 4.5.1.Final
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ always
+
+
+
+
+
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsClientTemplate.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsClientTemplate.java
new file mode 100644
index 00000000..07abea3d
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsClientTemplate.java
@@ -0,0 +1,79 @@
+/*
+ * 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.*;
+import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig;
+import com.alibaba.csp.sentinel.adapter.jaxrs.future.FutureWrapper;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.util.function.Supplier;
+
+import javax.ws.rs.core.Response;
+import java.util.concurrent.Future;
+
+
+/**
+ * wrap jax-rs client execution with sentinel
+ *
+ * Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() {
+ *
+ * @Override
+ * public Response get() {
+ * return client.target(host).path(url).request()
+ * .get();
+ * }
+ * });
+ *
+ * @author sea
+ */
+public class SentinelJaxRsClientTemplate {
+
+ /**
+ * execute supplier with sentinel
+ * @param resourceName
+ * @param supplier
+ * @return
+ */
+ public static Response execute(String resourceName, Supplier supplier) {
+ Entry entry = null;
+ try {
+ entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
+ return supplier.get();
+ } catch (BlockException ex) {
+ return SentinelJaxRsConfig.getJaxRsFallback().fallbackResponse(resourceName, ex);
+ } finally {
+ System.out.println("entry exit");
+ if (entry != null) {
+ entry.exit();
+ }
+ }
+ }
+
+ /**
+ * execute supplier with sentinel
+ * @param resourceName
+ * @param supplier
+ * @return
+ */
+ public static Future executeAsync(String resourceName, Supplier> supplier) {
+ try {
+ AsyncEntry entry = SphU.asyncEntry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
+ return new FutureWrapper<>(entry, supplier.get());
+ } catch (BlockException ex) {
+ return SentinelJaxRsConfig.getJaxRsFallback().fallbackFutureResponse(resourceName, ex);
+ }
+ }
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsProviderFilter.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsProviderFilter.java
new file mode 100644
index 00000000..ffe4edb2
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsProviderFilter.java
@@ -0,0 +1,88 @@
+/*
+ * 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.*;
+import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig;
+import com.alibaba.csp.sentinel.context.ContextUtil;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import javax.ws.rs.container.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+
+/**
+ * @author sea
+ */
+@Provider
+public class SentinelJaxRsProviderFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+ private static final String SENTINEL_JAX_RS_PROVIDER_CONTEXT_NAME = "sentinel_jax_rs_provider_context";
+
+
+ private static final String SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY = "sentinel_jax_rs_provider_entry_property";
+
+ @Context
+ private ResourceInfo resourceInfo;
+
+ @Override
+ public void filter(ContainerRequestContext containerRequestContext) throws IOException {
+
+ try {
+ String resourceName = getResourceName(containerRequestContext, resourceInfo);
+
+ if (StringUtil.isNotEmpty(resourceName)) {
+ // Parse the request origin using registered origin parser.
+ String origin = parseOrigin(containerRequestContext);
+ String contextName = getContextName(containerRequestContext);
+ ContextUtil.enter(contextName, origin);
+ Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
+
+ containerRequestContext.setProperty(SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY, entry);
+ }
+ } catch (BlockException e) {
+ try {
+ containerRequestContext.abortWith(SentinelJaxRsConfig.getJaxRsFallback().fallbackResponse(containerRequestContext.getUriInfo().getPath(), e));
+ } finally {
+ ContextUtil.exit();
+ }
+ }
+ }
+
+ @Override
+ public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
+ Entry entry = (Entry) containerRequestContext.getProperty(SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY);
+ if (entry != null) {
+ entry.exit();
+ }
+ containerRequestContext.removeProperty(SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY);
+ ContextUtil.exit();
+ }
+
+ public String getResourceName(ContainerRequestContext containerRequestContext, ResourceInfo resourceInfo) {
+ return SentinelJaxRsConfig.getResourceNameParser().parse(containerRequestContext, resourceInfo);
+ }
+
+ protected String getContextName(ContainerRequestContext request) {
+ return SENTINEL_JAX_RS_PROVIDER_CONTEXT_NAME;
+ }
+
+ protected String parseOrigin(ContainerRequestContext request) {
+ return SentinelJaxRsConfig.getRequestOriginParser().parseOrigin(request);
+ }
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/config/SentinelJaxRsConfig.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/config/SentinelJaxRsConfig.java
new file mode 100644
index 00000000..a2684ed2
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/config/SentinelJaxRsConfig.java
@@ -0,0 +1,62 @@
+/*
+ * 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.config;
+
+import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.DefaultSentinelJaxRsFallback;
+import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback;
+import com.alibaba.csp.sentinel.adapter.jaxrs.request.DefaultRequestOriginParser;
+import com.alibaba.csp.sentinel.adapter.jaxrs.request.DefaultResourceNameParser;
+import com.alibaba.csp.sentinel.adapter.jaxrs.request.RequestOriginParser;
+import com.alibaba.csp.sentinel.adapter.jaxrs.request.ResourceNameParser;
+
+/**
+ * @author sea
+ */
+public class SentinelJaxRsConfig {
+
+ private static volatile ResourceNameParser resourceNameParser = new DefaultResourceNameParser();
+
+ private static volatile RequestOriginParser requestOriginParser = new DefaultRequestOriginParser();
+
+ private static volatile SentinelJaxRsFallback jaxRsFallback = new DefaultSentinelJaxRsFallback();
+
+ public static ResourceNameParser getResourceNameParser() {
+ return resourceNameParser;
+ }
+
+ public static void setResourceNameParser(ResourceNameParser resourceNameParser) {
+ SentinelJaxRsConfig.resourceNameParser = resourceNameParser;
+ }
+
+ public static RequestOriginParser getRequestOriginParser() {
+ return requestOriginParser;
+ }
+
+ public static void setRequestOriginParser(RequestOriginParser originParser) {
+ SentinelJaxRsConfig.requestOriginParser = originParser;
+ }
+
+ public static SentinelJaxRsFallback getJaxRsFallback() {
+ return jaxRsFallback;
+ }
+
+ public static void setJaxRsFallback(SentinelJaxRsFallback jaxRsFallback) {
+ SentinelJaxRsConfig.jaxRsFallback = jaxRsFallback;
+ }
+
+ private SentinelJaxRsConfig() {
+ }
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/exception/DefaultExceptionMapper.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/exception/DefaultExceptionMapper.java
new file mode 100644
index 00000000..a441204a
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/exception/DefaultExceptionMapper.java
@@ -0,0 +1,39 @@
+/*
+ * 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.exception;
+
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * sentinel jax-rs adapter provide this exception mapper
+ * in case of user throw exception which is not {@link javax.ws.rs.WebApplicationException} and not matched by any ExceptionMapper
+ * this exception mapper convert exception to Response let ContainerResponseFilter to be called to exit sentinel entry
+ * user can add custom ExceptionMapper and config with {@link javax.annotation.Priority} with lower value
+ * @author sea
+ */
+@Provider
+public class DefaultExceptionMapper implements ExceptionMapper {
+
+ @Override
+ public Response toResponse(Throwable exception) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity(exception.getMessage())
+ .build();
+ }
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/DefaultSentinelJaxRsFallback.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/DefaultSentinelJaxRsFallback.java
new file mode 100644
index 00000000..d1f93772
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/DefaultSentinelJaxRsFallback.java
@@ -0,0 +1,45 @@
+/*
+ * 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.fallback;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+
+/**
+ * @author sea
+ */
+public class DefaultSentinelJaxRsFallback implements SentinelJaxRsFallback {
+ @Override
+ public Response fallbackResponse(String route, Throwable cause) {
+ return Response.status(Response.Status.TOO_MANY_REQUESTS)
+ .entity("Blocked by Sentinel (flow limiting)")
+ .type(MediaType.APPLICATION_JSON_TYPE)
+ .build();
+ }
+
+ @Override
+ public Future fallbackFutureResponse(final String route, final Throwable cause) {
+ return new FutureTask<>(new Callable() {
+ @Override
+ public Response call() throws Exception {
+ return fallbackResponse(route, cause);
+ }
+ });
+ }
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/SentinelJaxRsFallback.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/SentinelJaxRsFallback.java
new file mode 100644
index 00000000..b5cf6115
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/SentinelJaxRsFallback.java
@@ -0,0 +1,43 @@
+/*
+ * 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.fallback;
+
+import javax.ws.rs.core.Response;
+import java.util.concurrent.Future;
+
+/**
+ * @author sea
+ */
+public interface SentinelJaxRsFallback {
+
+ /**
+ * Provides a fallback response based on the cause of the failed execution.
+ *
+ * @param route The route the fallback is for
+ * @param cause cause of the main method failure, may be null
+ * @return the fallback response
+ */
+ Response fallbackResponse(String route, Throwable cause);
+
+ /**
+ * Provides a fallback response future based on the cause of the failed execution.
+ *
+ * @param route The route the fallback is for
+ * @param cause cause of the main method failure, may be null
+ * @return the fallback response future
+ */
+ Future fallbackFutureResponse(String route, Throwable cause);
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/future/FutureWrapper.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/future/FutureWrapper.java
new file mode 100644
index 00000000..bc332114
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/future/FutureWrapper.java
@@ -0,0 +1,81 @@
+/*
+ * 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.future;
+
+import com.alibaba.csp.sentinel.AsyncEntry;
+
+import java.util.concurrent.*;
+
+/**
+ * wrap Future to ensure entry exit
+ * @author sea
+ */
+public class FutureWrapper implements Future {
+
+ AsyncEntry entry;
+
+ Future future;
+
+ public FutureWrapper(AsyncEntry entry, Future future) {
+ this.entry = entry;
+ this.future = future;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ try {
+ return future.cancel(mayInterruptIfRunning);
+ } finally {
+ exitEntry();
+ }
+
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return future.isCancelled();
+ }
+
+ @Override
+ public boolean isDone() {
+ return future.isDone();
+ }
+
+ @Override
+ public V get() throws InterruptedException, ExecutionException {
+ try {
+ return future.get();
+ } finally {
+ exitEntry();
+ }
+ }
+
+ @Override
+ public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ try {
+ return future.get(timeout, unit);
+ } finally {
+ exitEntry();
+ }
+ }
+
+ private void exitEntry() {
+ if (entry != null) {
+ entry.exit();
+ }
+ }
+
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultRequestOriginParser.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultRequestOriginParser.java
new file mode 100644
index 00000000..b8ab0f9c
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultRequestOriginParser.java
@@ -0,0 +1,31 @@
+/*
+ * 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.request;
+
+import javax.ws.rs.container.ContainerRequestContext;
+
+/**
+ * @author sea
+ */
+public class DefaultRequestOriginParser implements RequestOriginParser {
+
+ private static final String EMPTY_ORIGIN = "";
+
+ @Override
+ public String parseOrigin(ContainerRequestContext request) {
+ return EMPTY_ORIGIN;
+ }
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultResourceNameParser.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultResourceNameParser.java
new file mode 100644
index 00000000..efc8c7ed
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultResourceNameParser.java
@@ -0,0 +1,33 @@
+/*
+ * 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.request;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ResourceInfo;
+
+/**
+ * @author sea
+ */
+public class DefaultResourceNameParser implements ResourceNameParser {
+ @Override
+ public String parse(ContainerRequestContext containerRequestContext, ResourceInfo resourceInfo) {
+ Path classPath = resourceInfo.getResourceClass().getAnnotation(Path.class);
+ Path methodPath = resourceInfo.getResourceMethod().getAnnotation(Path.class);
+ return containerRequestContext.getRequest().getMethod() + ":" + (classPath != null ? classPath.value() : "") + (methodPath != null ? methodPath.value() : "");
+ }
+
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/RequestOriginParser.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/RequestOriginParser.java
new file mode 100644
index 00000000..0eff671f
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/RequestOriginParser.java
@@ -0,0 +1,34 @@
+/*
+ * 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.request;
+
+import javax.ws.rs.container.ContainerRequestContext;
+
+/**
+ * The origin parser parses request origin (e.g. IP, user, appName) from HTTP request.
+ *
+ * @author sea
+ */
+public interface RequestOriginParser {
+
+ /**
+ * Parse the origin from given HTTP request.
+ *
+ * @param request HTTP request
+ * @return parsed origin
+ */
+ String parseOrigin(ContainerRequestContext request);
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/ResourceNameParser.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/ResourceNameParser.java
new file mode 100644
index 00000000..096c31bf
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/ResourceNameParser.java
@@ -0,0 +1,27 @@
+/*
+ * 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.request;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ResourceInfo;
+
+/**
+ * @author sea
+ */
+public interface ResourceNameParser {
+
+ String parse(ContainerRequestContext containerRequestContext, ResourceInfo resourceInfo);
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ClientFilterTest.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ClientFilterTest.java
new file mode 100644
index 00000000..91c01e11
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ClientFilterTest.java
@@ -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() {
+
+ @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 future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() {
+ @Override
+ public Future 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() {
+ @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() {
+ @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() {
+ @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 fallbackFutureResponse(final String route, final Throwable cause) {
+ return new FutureTask<>(new Callable() {
+ @Override
+ public Response call() throws Exception {
+ return fallbackResponse(route, cause);
+ }
+ });
+ }
+ });
+
+ Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() {
+ @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() {
+ @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() {
+ @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() {
+ @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 future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() {
+ @Override
+ public Future 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 future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() {
+ @Override
+ public Future 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 future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() {
+ @Override
+ public Future 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));
+ }
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ProviderFilterTest.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ProviderFilterTest.java
new file mode 100644
index 00000000..30614404
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ProviderFilterTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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 java.util.Collections;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+
+import com.alibaba.csp.sentinel.Constants;
+import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig;
+import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback;
+import com.alibaba.csp.sentinel.adapter.jaxrs.request.RequestOriginParser;
+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 io.restassured.RestAssured;
+import io.restassured.response.Response;
+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.container.ContainerRequestContext;
+import javax.ws.rs.core.MediaType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.*;
+
+/**
+ * @author sea
+ */
+public class ProviderFilterTest {
+
+ private static final String HELLO_STR = "Hello!";
+
+ static ConfigurableApplicationContext ctx;
+
+ @BeforeClass
+ public static void startApplication() {
+ RestAssured.basePath = "";
+ int port = SocketUtils.findAvailableTcpPort();
+ RestAssured.port = port;
+ SpringApplication springApplication = new SpringApplication(TestApplication.class);
+ ctx = springApplication.run("--spring.profiles.active=provider", "--server.port=" + port);
+ }
+
+ @AfterClass
+ public static void shutdown() {
+ ctx.close();
+ }
+
+ @After
+ public void cleanUp() {
+ FlowRuleManager.loadRules(null);
+ ClusterBuilderSlot.resetClusterNodes();
+ }
+
+
+ @Test
+ public void testGetHello() {
+ String url = "/test/hello";
+ String resourceName = "GET:" + url;
+ Response response = given().get(url);
+ response.then().statusCode(200).body(equalTo(HELLO_STR));
+
+ 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 testAsyncGetHello() {
+ String url = "/test/async-hello";
+ String resourceName = "GET:" + url;
+ Response response = given().get(url);
+ response.then().statusCode(200).body(equalTo(HELLO_STR));
+
+ 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 testUrlPathParam() {
+ String url = "/test/hello/{name}";
+ String resourceName = "GET:" + url;
+
+ String url1 = "/test/hello/abc";
+ Response response1 = given().get(url1);
+ response1.then().statusCode(200).body(equalTo("Hello abc !"));
+
+ String url2 = "/test/hello/def";
+ Response response2 = given().get(url2);
+ response2.then().statusCode(200).body(equalTo("Hello def !"));
+
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
+ assertNotNull(cn);
+ assertEquals(2, cn.passQps(), 0.01);
+
+ assertNull(ClusterBuilderSlot.getClusterNode("GET:" + url1));
+ assertNull(ClusterBuilderSlot.getClusterNode("GET:" + url2));
+ }
+
+ @Test
+ public void testDefaultFallback() {
+ String url = "/test/hello";
+ String resourceName = "GET:" + url;
+ configureRulesFor(resourceName, 0);
+ Response response = given().get(url);
+ response.then().statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode())
+ .body(equalTo("Blocked by Sentinel (flow limiting)"));
+
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
+ assertNotNull(cn);
+ assertEquals(0, cn.passQps(), 0.01);
+ }
+
+ @Test
+ public void testCustomFallback() {
+ String url = "/test/hello";
+ String resourceName = "GET:" + url;
+ 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 fallbackFutureResponse(final String route, final Throwable cause) {
+ return new FutureTask<>(new Callable() {
+ @Override
+ public javax.ws.rs.core.Response call() throws Exception {
+ return fallbackResponse(route, cause);
+ }
+ });
+ }
+ });
+
+
+ configureRulesFor(resourceName, 0);
+ Response response = given().get(url);
+ response.then().statusCode(javax.ws.rs.core.Response.Status.OK.getStatusCode())
+ .body(equalTo("Blocked by Sentinel (flow limiting)"));
+
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
+ assertNotNull(cn);
+ assertEquals(0, cn.passQps(), 0.01);
+ }
+
+ @Test
+ public void testCustomRequestOriginParser() {
+ String url = "/test/hello";
+ String resourceName = "GET:" + url;
+
+ String limitOrigin = "appB";
+ final String headerName = "X-APP";
+ configureRulesFor(resourceName, 0, limitOrigin);
+
+ SentinelJaxRsConfig.setRequestOriginParser(new RequestOriginParser() {
+ @Override
+ public String parseOrigin(ContainerRequestContext request) {
+ String origin = request.getHeaderString(headerName);
+ return origin != null ? origin : "";
+ }
+ });
+
+ Response response = given()
+ .header(headerName, "appA").get(url);
+ response.then().statusCode(200).body(equalTo(HELLO_STR));
+
+ Response blockedResp = given()
+ .header(headerName, "appB")
+ .get(url);
+ blockedResp.then().statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode())
+ .body(equalTo("Blocked by Sentinel (flow limiting)"));
+
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
+ assertNotNull(cn);
+ assertEquals(1, cn.passQps(), 0.01);
+ assertEquals(1, cn.blockQps(), 0.01);
+ }
+
+ @Test
+ public void testExceptionMapper() {
+ String url = "/test/ex";
+ String resourceName = "GET:" + url;
+ Response response = given().get(url);
+ response.then().statusCode(javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()).body(equalTo("test exception mapper"));
+
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
+ assertNotNull(cn);
+ }
+
+ 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));
+ }
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestApplication.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestApplication.java
new file mode 100644
index 00000000..6ffafb95
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestApplication.java
@@ -0,0 +1,30 @@
+/*
+ * 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 org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author sea
+ */
+@SpringBootApplication
+public class TestApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(TestApplication.class, args);
+ }
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestResource.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestResource.java
new file mode 100644
index 00000000..f5d8bf18
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestResource.java
@@ -0,0 +1,92 @@
+/*
+ * 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 org.springframework.stereotype.Component;
+
+import javax.ws.rs.*;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author sea
+ */
+@Path("/test")
+@Component
+public class TestResource {
+
+ ExecutorService executor = Executors.newFixedThreadPool(5);
+
+ @Path("/hello")
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String sayHello() {
+ return "Hello!";
+ }
+
+ @Path("/async-hello")
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON })
+ public void asyncSayHello(@Suspended final AsyncResponse asyncResponse) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ TimeUnit.MILLISECONDS.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ asyncResponse.resume("Hello!");
+ }
+ });
+ }
+
+ @Path("/hello/{name}")
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String sayHelloWithName(@PathParam(value = "name") String name) {
+ return "Hello " + name + " !";
+ }
+
+ @Path("/ex")
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String exception() {
+ throw new RuntimeException("test exception mapper");
+ }
+
+ @Path("/400")
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String badRequest() {
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST)
+ .entity("test return 400")
+ .build());
+ }
+
+ @Path("/delay/{seconds}")
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String delay(@PathParam(value = "seconds") long seconds) throws InterruptedException {
+ TimeUnit.SECONDS.sleep(seconds);
+ return "finish";
+ }
+}
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-client.yml b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-client.yml
new file mode 100644
index 00000000..af8e43ed
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-client.yml
@@ -0,0 +1,3 @@
+resteasy:
+ jaxrs:
+ scan-packages: com.alibaba.csp.sentinel.adapter.jaxrs.exception
diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-provider.yml b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-provider.yml
new file mode 100644
index 00000000..dc47def2
--- /dev/null
+++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-provider.yml
@@ -0,0 +1,3 @@
+resteasy:
+ jaxrs:
+ scan-packages: com.alibaba.csp.sentinel.adapter.jaxrs
diff --git a/sentinel-demo/pom.xml b/sentinel-demo/pom.xml
index 04c07ed1..a5465da0 100755
--- a/sentinel-demo/pom.xml
+++ b/sentinel-demo/pom.xml
@@ -39,6 +39,7 @@
sentinel-demo-spring-webmvc
sentinel-demo-zuul2-gateway
sentinel-demo-log-logback
+ sentinel-demo-jax-rs
diff --git a/sentinel-demo/sentinel-demo-jax-rs/pom.xml b/sentinel-demo/sentinel-demo-jax-rs/pom.xml
new file mode 100644
index 00000000..d2dc06b3
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-jax-rs/pom.xml
@@ -0,0 +1,49 @@
+
+
+
+ sentinel-demo
+ com.alibaba.csp
+ 1.8.0-SNAPSHOT
+
+ 4.0.0
+
+ sentinel-demo-jax-rs
+
+
+ 2.2.6.RELEASE
+
+
+
+
+ com.alibaba.csp
+ sentinel-core
+
+
+ com.alibaba.csp
+ sentinel-transport-simple-http
+
+
+ com.alibaba.csp
+ sentinel-jax-rs-adapter
+ ${project.version}
+
+
+ org.jboss.resteasy
+ resteasy-spring-boot-starter
+ 4.5.1.Final
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring.boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ ${spring.boot.version}
+
+
+
+
diff --git a/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/CustomExceptionMapper.java b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/CustomExceptionMapper.java
new file mode 100644
index 00000000..feb76a31
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/CustomExceptionMapper.java
@@ -0,0 +1,38 @@
+/*
+ * 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.demo.jaxrs;
+
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Priority;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * @author sea
+ */
+@Component
+@Provider
+@Priority(javax.ws.rs.Priorities.USER - 1)
+public class CustomExceptionMapper implements ExceptionMapper {
+ @Override
+ public Response toResponse(Throwable exception) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity("Unknown Server Error")
+ .build();
+ }
+}
diff --git a/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/HelloEntity.java b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/HelloEntity.java
new file mode 100644
index 00000000..4b17ebee
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/HelloEntity.java
@@ -0,0 +1,62 @@
+/*
+ * 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.demo.jaxrs;
+
+/**
+ * @author sea
+ */
+public class HelloEntity {
+
+ Long id;
+
+ String msg;
+
+ public HelloEntity() {
+ }
+
+ public HelloEntity(String msg) {
+ this.msg = msg;
+ }
+
+ public HelloEntity(Long id, String msg) {
+ this.id = id;
+ this.msg = msg;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ @Override
+ public String toString() {
+ return "HelloEntity{" +
+ "id=" + id +
+ ", msg='" + msg + '\'' +
+ '}';
+ }
+}
diff --git a/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/HelloResource.java b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/HelloResource.java
new file mode 100644
index 00000000..58e59d64
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/HelloResource.java
@@ -0,0 +1,63 @@
+/*
+ * 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.demo.jaxrs;
+
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * HelloResource
+ * @author sea
+ */
+@Path("/hello")
+@Produces(MediaType.APPLICATION_JSON)
+@Component
+public class HelloResource {
+
+ @GET
+ public HelloEntity sayHello() {
+ return new HelloEntity("hello");
+ }
+
+ @GET
+ @Path("/{id}")
+ public HelloEntity get(@PathParam(value = "id") Long id) {
+ return new HelloEntity(id, "hello");
+ }
+
+ @GET
+ @Path("/list")
+ public List getAll() {
+ return IntStream.rangeClosed(1, 1000)
+ .mapToObj(i -> new HelloEntity((long)i, "hello"))
+ .collect(Collectors.toList());
+ }
+
+ @Path("/ex")
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String exception() {
+ throw new RuntimeException("test exception mapper");
+ }
+}
diff --git a/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/JaxRsClientDemo.java b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/JaxRsClientDemo.java
new file mode 100644
index 00000000..c273b310
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/JaxRsClientDemo.java
@@ -0,0 +1,51 @@
+/*
+ * 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.demo.jaxrs;
+
+import com.alibaba.csp.sentinel.adapter.jaxrs.SentinelJaxRsClientTemplate;
+import com.alibaba.csp.sentinel.util.function.Supplier;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author sea
+ */
+public class JaxRsClientDemo {
+
+ public static void main(String[] args) {
+ Client client = ClientBuilder.newBuilder()
+ .connectTimeout(3, TimeUnit.SECONDS)
+ .readTimeout(3, TimeUnit.SECONDS)
+ .build();
+
+ final String host = "http://127.0.0.1:8181";
+ final String url = "/hello/1";
+ String resourceName = "GET:" + url;
+ Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() {
+
+ @Override
+ public Response get() {
+ return client.target(host).path(url).request()
+ .get();
+ }
+ });
+ System.out.println(response.readEntity(HelloEntity.class));
+ System.exit(0);
+ }
+}
diff --git a/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/JaxRsDemoApplication.java b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/JaxRsDemoApplication.java
new file mode 100644
index 00000000..460f29ee
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/JaxRsDemoApplication.java
@@ -0,0 +1,33 @@
+/*
+ * 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.demo.jaxrs;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Add the JVM parameter to connect to the dashboard:
+ * {@code -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-jax-rs}
+ *
+ * @author sea
+ */
+@SpringBootApplication
+public class JaxRsDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(JaxRsDemoApplication.class);
+ }
+}
diff --git a/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/SentinelJaxRsConfig.java b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/SentinelJaxRsConfig.java
new file mode 100644
index 00000000..fbabc214
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/SentinelJaxRsConfig.java
@@ -0,0 +1,32 @@
+/*
+ * 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.demo.jaxrs;
+
+import com.alibaba.csp.sentinel.adapter.jaxrs.SentinelJaxRsProviderFilter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author sea
+ */
+@Configuration(proxyBeanMethods = false)
+public class SentinelJaxRsConfig {
+
+ @Bean
+ public SentinelJaxRsProviderFilter sentinelJaxRsProviderFilter() {
+ return new SentinelJaxRsProviderFilter();
+ }
+}
diff --git a/sentinel-demo/sentinel-demo-jax-rs/src/main/resources/application.properties b/sentinel-demo/sentinel-demo-jax-rs/src/main/resources/application.properties
new file mode 100644
index 00000000..d636638d
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-jax-rs/src/main/resources/application.properties
@@ -0,0 +1 @@
+server.port=8181