sentinel-web-servlet
+ sentinel-web-servlet-jakarta
sentinel-dubbo-adapter
sentinel-apache-dubbo-adapter
sentinel-apache-dubbo3-adapter
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/pom.xml b/sentinel-adapter/sentinel-web-servlet-jakarta/pom.xml
new file mode 100644
index 0000000000..2e02d164d5
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+
+ com.alibaba.csp
+ sentinel-adapter
+ 2.0.0-alpha2-SNAPSHOT
+
+
+ sentinel-web-servlet-jakarta
+ jar
+ Sentinel adapter for Jakarta EE Servlet 6.0 (Spring Boot 3.x)
+
+
+ 6.0.0
+
+
+
+
+ com.alibaba.csp
+ sentinel-core
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ ${jakarta.servlet-api.version}
+ provided
+
+
+
+ junit
+ junit
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ 3.2.0
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 3.2.0
+ test
+
+
+
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java
new file mode 100644
index 0000000000..62e148bd25
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java
@@ -0,0 +1,144 @@
+/*
+ * 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.io.IOException;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import com.alibaba.csp.sentinel.Entry;
+import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.ResourceTypeConstants;
+import com.alibaba.csp.sentinel.SphU;
+import com.alibaba.csp.sentinel.Tracer;
+import com.alibaba.csp.sentinel.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.context.ContextUtil;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * Servlet filter that integrates with Sentinel.
+ *
+ * @author youji.zj
+ * @author Eric Zhao
+ * @author zhaoyuguang
+ */
+public class CommonFilter implements Filter {
+
+ /**
+ * Specify whether the URL resource name should contain the HTTP method prefix (e.g. {@code POST:}).
+ */
+ public static final String HTTP_METHOD_SPECIFY = "HTTP_METHOD_SPECIFY";
+ /**
+ * If enabled, use the default context name, or else use the URL path as the context name,
+ * {@link WebServletConfig#WEB_SERVLET_CONTEXT_NAME}. Please pay attention to the number of context (EntranceNode),
+ * which may affect the memory footprint.
+ *
+ * @since 1.7.0
+ */
+ public static final String WEB_CONTEXT_UNIFY = "WEB_CONTEXT_UNIFY";
+
+ private final static String COLON = ":";
+
+ private boolean httpMethodSpecify = false;
+ private boolean webContextUnify = true;
+
+ @Override
+ public void init(FilterConfig filterConfig) {
+ httpMethodSpecify = Boolean.parseBoolean(filterConfig.getInitParameter(HTTP_METHOD_SPECIFY));
+ if (filterConfig.getInitParameter(WEB_CONTEXT_UNIFY) != null) {
+ webContextUnify = Boolean.parseBoolean(filterConfig.getInitParameter(WEB_CONTEXT_UNIFY));
+ }
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ HttpServletRequest sRequest = (HttpServletRequest) request;
+ Entry urlEntry = null;
+
+ try {
+ String target = FilterUtil.filterTarget(sRequest);
+ // Clean and unify the URL.
+ // For REST APIs, you have to clean the URL (e.g. `/foo/1` and `/foo/2` -> `/foo/:id`), or
+ // the amount of context and resources will exceed the threshold.
+ UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
+ if (urlCleaner != null) {
+ target = urlCleaner.clean(target);
+ }
+
+ // If you intend to exclude some URLs, you can convert the URLs to the empty string ""
+ // in the UrlCleaner implementation.
+ if (!StringUtil.isEmpty(target)) {
+ // Parse the request origin using registered origin parser.
+ String origin = parseOrigin(sRequest);
+ String contextName = webContextUnify ? WebServletConfig.WEB_SERVLET_CONTEXT_NAME : target;
+ ContextUtil.enter(contextName, origin);
+
+ if (httpMethodSpecify) {
+ // Add HTTP method prefix if necessary.
+ String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + COLON + target;
+ urlEntry = SphU.entry(pathWithHttpMethod, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
+ } else {
+ urlEntry = SphU.entry(target, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
+ }
+ }
+ chain.doFilter(request, response);
+ } catch (BlockException e) {
+ HttpServletResponse sResponse = (HttpServletResponse) response;
+ // Return the block page, or redirect to another URL.
+ WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e);
+ } catch (IOException | ServletException | RuntimeException e2) {
+ Tracer.traceEntry(e2, urlEntry);
+ throw e2;
+ } finally {
+ if (urlEntry != null) {
+ urlEntry.exit();
+ }
+ ContextUtil.exit();
+ }
+ }
+
+ private String parseOrigin(HttpServletRequest request) {
+ RequestOriginParser originParser = WebCallbackManager.getRequestOriginParser();
+ String origin = EMPTY_ORIGIN;
+ if (originParser != null) {
+ origin = originParser.parseOrigin(request);
+ if (StringUtil.isEmpty(origin)) {
+ return EMPTY_ORIGIN;
+ }
+ }
+ return origin;
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+ private static final String EMPTY_ORIGIN = "";
+}
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonTotalFilter.java b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonTotalFilter.java
new file mode 100644
index 0000000000..a58e431dbf
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonTotalFilter.java
@@ -0,0 +1,81 @@
+/*
+ * 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.io.IOException;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import com.alibaba.csp.sentinel.Entry;
+import com.alibaba.csp.sentinel.ResourceTypeConstants;
+import com.alibaba.csp.sentinel.SphU;
+import com.alibaba.csp.sentinel.Tracer;
+import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
+import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig;
+import com.alibaba.csp.sentinel.context.ContextUtil;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+
+/***
+ * Servlet filter for all requests.
+ *
+ * @author youji.zj
+ */
+public class CommonTotalFilter implements Filter {
+
+ public static final String TOTAL_URL_REQUEST = "total-url-request";
+
+ @Override
+ public void init(FilterConfig filterConfig) {
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest sRequest = (HttpServletRequest)request;
+
+ Entry entry = null;
+ try {
+ ContextUtil.enter(WebServletConfig.WEB_SERVLET_CONTEXT_NAME);
+ entry = SphU.entry(TOTAL_URL_REQUEST, ResourceTypeConstants.COMMON_WEB);
+ chain.doFilter(request, response);
+ } catch (BlockException e) {
+ HttpServletResponse sResponse = (HttpServletResponse)response;
+ WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e);
+ } catch (IOException | ServletException | RuntimeException e2) {
+ Tracer.trace(e2);
+ throw e2;
+ } finally {
+ if (entry != null) {
+ entry.exit();
+ }
+ ContextUtil.exit();
+ }
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+}
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlBlockHandler.java b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlBlockHandler.java
new file mode 100644
index 0000000000..7f39a06c4d
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlBlockHandler.java
@@ -0,0 +1,39 @@
+/*
+ * 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.callback;
+
+import java.io.IOException;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+
+/***
+ * The default {@link UrlBlockHandler}.
+ *
+ * @author youji.zj
+ */
+public class DefaultUrlBlockHandler implements UrlBlockHandler {
+
+ @Override
+ public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex)
+ throws IOException {
+ // Directly redirect to the default flow control (blocked) page or customized block page.
+ FilterUtil.blockRequest(request, response);
+ }
+}
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlCleaner.java b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlCleaner.java
new file mode 100644
index 0000000000..2d80abd463
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlCleaner.java
@@ -0,0 +1,27 @@
+/*
+ * 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.callback;
+
+/***
+ * @author youji.zj
+ */
+public class DefaultUrlCleaner implements UrlCleaner {
+
+ @Override
+ public String clean(String originUrl) {
+ return originUrl;
+ }
+}
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/RequestOriginParser.java b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/RequestOriginParser.java
new file mode 100644
index 0000000000..da127a26ed
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/RequestOriginParser.java
@@ -0,0 +1,35 @@
+/*
+ * 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.callback;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * The origin parser parses request origin (e.g. IP, user, appName) from HTTP request.
+ *
+ * @author Eric Zhao
+ * @since 0.2.0
+ */
+public interface RequestOriginParser {
+
+ /**
+ * Parse the origin from given HTTP request.
+ *
+ * @param request HTTP request
+ * @return parsed origin
+ */
+ String parseOrigin(HttpServletRequest request);
+}
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlBlockHandler.java b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlBlockHandler.java
new file mode 100644
index 0000000000..613cb3204c
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlBlockHandler.java
@@ -0,0 +1,41 @@
+/*
+ * 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.callback;
+
+import java.io.IOException;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+
+/***
+ * The URL block handler handles requests when blocked.
+ *
+ * @author youji.zj
+ */
+public interface UrlBlockHandler {
+
+ /**
+ * Handle the request when blocked.
+ *
+ * @param request Servlet request
+ * @param response Servlet response
+ * @param ex the block exception.
+ * @throws IOException some error occurs
+ */
+ void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException;
+}
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlCleaner.java b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlCleaner.java
new file mode 100644
index 0000000000..c5883396bf
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlCleaner.java
@@ -0,0 +1,31 @@
+/*
+ * 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.callback;
+
+/***
+ * @author youji.zj
+ */
+public interface UrlCleaner {
+
+ /***
+ * Process the url. Some path variables should be handled and unified.
+ * e.g. collect_item_relation--10200012121-.html will be converted to collect_item_relation.html
+ *
+ * @param originUrl original url
+ * @return processed url
+ */
+ String clean(String originUrl);
+}
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/WebCallbackManager.java b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/WebCallbackManager.java
new file mode 100644
index 0000000000..73231bcf8e
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/WebCallbackManager.java
@@ -0,0 +1,63 @@
+/*
+ * 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.callback;
+
+import com.alibaba.csp.sentinel.util.AssertUtil;
+
+/**
+ * Registry for URL cleaner and URL block handler.
+ *
+ * @author youji.zj
+ */
+public class WebCallbackManager {
+
+ /**
+ * URL cleaner.
+ */
+ private static volatile UrlCleaner urlCleaner = new DefaultUrlCleaner();
+
+ /**
+ * URL block handler.
+ */
+ private static volatile UrlBlockHandler urlBlockHandler = new DefaultUrlBlockHandler();
+
+ private static volatile RequestOriginParser requestOriginParser = null;
+
+ public static UrlCleaner getUrlCleaner() {
+ return urlCleaner;
+ }
+
+ public static void setUrlCleaner(UrlCleaner urlCleaner) {
+ WebCallbackManager.urlCleaner = urlCleaner;
+ }
+
+ public static UrlBlockHandler getUrlBlockHandler() {
+ return urlBlockHandler;
+ }
+
+ public static void setUrlBlockHandler(UrlBlockHandler urlBlockHandler) {
+ AssertUtil.isTrue(urlBlockHandler != null, "URL block handler should not be null");
+ WebCallbackManager.urlBlockHandler = urlBlockHandler;
+ }
+
+ public static RequestOriginParser getRequestOriginParser() {
+ return requestOriginParser;
+ }
+
+ public static void setRequestOriginParser(RequestOriginParser requestOriginParser) {
+ WebCallbackManager.requestOriginParser = requestOriginParser;
+ }
+}
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/config/WebServletConfig.java b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/config/WebServletConfig.java
new file mode 100644
index 0000000000..dc6f66c42a
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/config/WebServletConfig.java
@@ -0,0 +1,94 @@
+/*
+ * 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.config;
+
+import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
+import com.alibaba.csp.sentinel.adapter.servlet.CommonTotalFilter;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * The configuration center for Web Servlet adapter.
+ *
+ * @author leyou
+ * @author zhaoyuguang
+ */
+public final class WebServletConfig {
+
+ public static final String WEB_SERVLET_CONTEXT_NAME = "sentinel_web_servlet_context";
+
+ public static final String BLOCK_PAGE_URL_CONF_KEY = "csp.sentinel.web.servlet.block.page";
+ public static final String BLOCK_PAGE_HTTP_STATUS_CONF_KEY = "csp.sentinel.web.servlet.block.status";
+
+ private static final int HTTP_STATUS_TOO_MANY_REQUESTS = 429;
+
+ /**
+ * Get redirecting page when Sentinel blocking for {@link CommonFilter} or
+ * {@link CommonTotalFilter} occurs.
+ *
+ * @return the block page URL, maybe null if not configured.
+ */
+ public static String getBlockPage() {
+ return SentinelConfig.getConfig(BLOCK_PAGE_URL_CONF_KEY);
+ }
+
+ public static void setBlockPage(String blockPage) {
+ SentinelConfig.setConfig(BLOCK_PAGE_URL_CONF_KEY, blockPage);
+ }
+
+ /**
+ * Get the HTTP status when using the default block page.
+ * You can set the status code with the {@code -Dcsp.sentinel.web.servlet.block.status}
+ * property. When the property is empty or invalid, Sentinel will use 429 (Too Many Requests)
+ * as the default status code.
+ *
+ * @return the HTTP status of the default block page
+ * @since 1.7.0
+ */
+ public static int getBlockPageHttpStatus() {
+ String value = SentinelConfig.getConfig(BLOCK_PAGE_HTTP_STATUS_CONF_KEY);
+ if (StringUtil.isEmpty(value)) {
+ return HTTP_STATUS_TOO_MANY_REQUESTS;
+ }
+ try {
+ int s = Integer.parseInt(value);
+ if (s <= 0) {
+ throw new IllegalArgumentException("Invalid status code: " + s);
+ }
+ return s;
+ } catch (Exception e) {
+ RecordLog.warn("[WebServletConfig] Invalid block HTTP status (" + value + "), using default 429");
+ setBlockPageHttpStatus(HTTP_STATUS_TOO_MANY_REQUESTS);
+ }
+ return HTTP_STATUS_TOO_MANY_REQUESTS;
+ }
+
+ /**
+ * Set the HTTP status of the default block page.
+ *
+ * @param httpStatus the HTTP status of the default block page
+ * @since 1.7.0
+ */
+ public static void setBlockPageHttpStatus(int httpStatus) {
+ if (httpStatus <= 0) {
+ throw new IllegalArgumentException("Invalid HTTP status code: " + httpStatus);
+ }
+ SentinelConfig.setConfig(BLOCK_PAGE_HTTP_STATUS_CONF_KEY, String.valueOf(httpStatus));
+ }
+
+ private WebServletConfig() {}
+}
diff --git a/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/util/FilterUtil.java b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/util/FilterUtil.java
new file mode 100644
index 0000000000..b9498ae84d
--- /dev/null
+++ b/sentinel-adapter/sentinel-web-servlet-jakarta/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/util/FilterUtil.java
@@ -0,0 +1,191 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * Util class for web servlet filter.
+ *
+ * @author zhaoyuguang
+ * @author youji.zj
+ * @author Eric Zhao
+ */
+public final class FilterUtil {
+
+ private static final String PATH_SPLIT = "/";
+
+ public static String filterTarget(HttpServletRequest request) {
+ String pathInfo = getResourcePath(request);
+ if (!pathInfo.startsWith(PATH_SPLIT)) {
+ pathInfo = PATH_SPLIT + pathInfo;
+ }
+
+ if (PATH_SPLIT.equals(pathInfo)) {
+ return pathInfo;
+ }
+
+ // Note: pathInfo should be converted to camelCase style.
+ int lastSlashIndex = pathInfo.lastIndexOf("/");
+
+ if (lastSlashIndex >= 0) {
+ pathInfo = pathInfo.substring(0, lastSlashIndex) + "/"
+ + StringUtil.trim(pathInfo.substring(lastSlashIndex + 1));
+ } else {
+ pathInfo = PATH_SPLIT + StringUtil.trim(pathInfo);
+ }
+
+ return pathInfo;
+ }
+
+ public static void blockRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ StringBuffer url = request.getRequestURL();
+
+ if ("GET".equals(request.getMethod()) && StringUtil.isNotBlank(request.getQueryString())) {
+ url.append("?").append(request.getQueryString());
+ }
+
+ if (StringUtil.isBlank(WebServletConfig.getBlockPage())) {
+ writeDefaultBlockedPage(response, WebServletConfig.getBlockPageHttpStatus());
+ } else {
+ String redirectUrl = WebServletConfig.getBlockPage() + "?http_referer=" + url.toString();
+ // Redirect to the customized block page.
+ response.sendRedirect(redirectUrl);
+ }
+ }
+
+ private static void writeDefaultBlockedPage(HttpServletResponse response, int httpStatus) throws IOException {
+ response.setStatus(httpStatus);
+ PrintWriter out = response.getWriter();
+ out.print(DEFAULT_BLOCK_MSG);
+ out.flush();
+ out.close();
+ }
+
+ private static String getResourcePath(HttpServletRequest request) {
+ String pathInfo = normalizeAbsolutePath(request.getPathInfo(), false);
+ String servletPath = normalizeAbsolutePath(request.getServletPath(), pathInfo.length() != 0);
+
+ return servletPath + pathInfo;
+ }
+
+ private static String normalizeAbsolutePath(String path, boolean removeTrailingSlash) throws IllegalStateException {
+ return normalizePath(path, true, false, removeTrailingSlash);
+ }
+
+ private static String normalizePath(String path, boolean forceAbsolute, boolean forceRelative,
+ boolean removeTrailingSlash) throws IllegalStateException {
+ char[] pathChars = StringUtil.trimToEmpty(path).toCharArray();
+ int length = pathChars.length;
+
+ // Check path and slash.
+ boolean startsWithSlash = false;
+ boolean endsWithSlash = false;
+
+ if (length > 0) {
+ char firstChar = pathChars[0];
+ char lastChar = pathChars[length - 1];
+
+ startsWithSlash = firstChar == PATH_SPLIT.charAt(0) || firstChar == '\\';
+ endsWithSlash = lastChar == PATH_SPLIT.charAt(0) || lastChar == '\\';
+ }
+
+ StringBuilder buf = new StringBuilder(length);
+ boolean isAbsolutePath = forceAbsolute || !forceRelative && startsWithSlash;
+ int index = startsWithSlash ? 0 : -1;
+ int level = 0;
+
+ if (isAbsolutePath) {
+ buf.append(PATH_SPLIT);
+ }
+
+ while (index < length) {
+ index = indexOfSlash(pathChars, index + 1, false);
+
+ if (index == length) {
+ break;
+ }
+
+ int nextSlashIndex = indexOfSlash(pathChars, index, true);
+
+ String element = new String(pathChars, index, nextSlashIndex - index);
+ index = nextSlashIndex;
+
+ // Ignore "."
+ if (".".equals(element)) {
+ continue;
+ }
+
+ // Backtrack ".."
+ if ("..".equals(element)) {
+ if (level == 0) {
+ if (isAbsolutePath) {
+ throw new IllegalStateException(path);
+ } else {
+ buf.append("..").append(PATH_SPLIT);
+ }
+ } else {
+ buf.setLength(pathChars[--level]);
+ }
+
+ continue;
+ }
+
+ pathChars[level++] = (char)buf.length();
+ buf.append(element).append(PATH_SPLIT);
+ }
+
+ // remove the last "/"
+ if (buf.length() > 0) {
+ if (!endsWithSlash || removeTrailingSlash) {
+ buf.setLength(buf.length() - 1);
+ }
+ }
+
+ return buf.toString();
+ }
+
+ private static int indexOfSlash(char[] chars, int beginIndex, boolean slash) {
+ int i = beginIndex;
+
+ for (; i < chars.length; i++) {
+ char ch = chars[i];
+
+ if (slash) {
+ if (ch == PATH_SPLIT.charAt(0) || ch == '\\') {
+ break; // if a slash
+ }
+ } else {
+ if (ch != PATH_SPLIT.charAt(0) && ch != '\\') {
+ break; // if not a slash
+ }
+ }
+ }
+
+ return i;
+ }
+
+ public static final String DEFAULT_BLOCK_MSG = "Blocked by Sentinel (flow limiting)";
+
+ private FilterUtil() {}
+}