This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
# Sentinel Web Servlet Filter
Sentinel provides Servlet filter integration to enable flow control for web requests.
Add the following dependency in `pom.xml` (if you are using Maven):
```xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
<version>x.y.z</version>
</dependency>
```
To activate the filter, you can simply configure your `web.xml` with:
```xml
<filter>
<filter-name>SentinelCommonFilter</filter-name>
<filter-class>com.alibaba.csp.sentinel.adapter.servlet.CommonFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SentinelCommonFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
```
For Spring web applications you can configure with Spring bean:
```java
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
registration.setFilter(new CommonFilter());
// Set the matching URL pattern for the filter.
registration.addUrlPatterns("/*");
registration.setName("sentinelCommonFilter");
registration.setOrder(1);
// Set whether to support the specified HTTP method prefix for the filter.
registration.addInitParameter(CommonFilter.HTTP_METHOD_SPECIFY, "false");
return registration;
}
}
```
When a request is blocked, Sentinel servlet filter will display a default page indicating the request is rejected.
The HTTP status code of the default block page is **429 (Too Many Requests)**. You can customize it
via the `csp.sentinel.web.servlet.block.status` configuration item (since 1.7.0).
If customized block page is set (via `WebServletConfig.setBlockPage(blockPage)` method),
the filter will redirect the request to provided URL. You can also implement your own
block handler (the `UrlBlockHandler` interface) and register to `WebCallbackManager`.
The `UrlCleaner` interface is designed for clean and unify the URL resource.
For REST APIs, you have to clean the URL resource (e.g. `/foo/1` and `/foo/2` -> `/foo/:id`), or
the amount of context and resources will exceed the threshold.
If you need to exclude some URLs (that should not be recorded as Sentinel resources), you could also
leverage the `UrlCleaner` interface. You may unify the unwanted URLs to the empty string `""` or `null`,
then the URLs will be excluded (since Sentinel 1.6.3).
The `RequestOriginParser` interface is useful for extracting request origin (e.g. IP or appName from HTTP Header)
from HTTP request. You can implement your own `RequestOriginParser` and register to `WebCallbackManager`.

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-adapter</artifactId>
<version>1.8.3</version>
</parent>
<artifactId>sentinel-web-servlet</artifactId>
<packaging>jar</packaging>
<properties>
<servlet.api.version>3.1.0</servlet.api.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.17.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>1.5.17.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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 javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.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 = "";
}

View File

@@ -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 javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.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() {
}
}

View File

@@ -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 javax.servlet.http.HttpServletRequest;
import javax.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);
}
}

View File

@@ -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;
}
}

View File

@@ -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 javax.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);
}

View File

@@ -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 javax.servlet.http.HttpServletRequest;
import javax.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;
}

View File

@@ -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 {
/***
* <p>Process the url. Some path variables should be handled and unified.</p>
* <p>e.g. collect_item_relation--10200012121-.html will be converted to collect_item_relation.html</p>
*
* @param originUrl original url
* @return processed url
*/
String clean(String originUrl);
}

View File

@@ -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;
}
}

View File

@@ -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);
}
/**
* <p>Get the HTTP status when using the default block page.</p>
* <p>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.</p>
*
* @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() {}
}

View File

@@ -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 javax.servlet.http.HttpServletRequest;
import javax.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() {}
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.adapter.servlet.callback.DefaultUrlCleaner;
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig;
import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.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 org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
/**
* @author zhaoyuguang
* @author Eric Zhao
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
@AutoConfigureMockMvc
public class CommonFilterTest {
private static final String HELLO_STR = "Hello!";
@Autowired
private MockMvc mvc;
private void configureRulesFor(String resource, int count) {
configureRulesFor(resource, count, "default");
}
private void configureRulesFor(String resource, int count, String limitApp) {
FlowRule rule = new FlowRule()
.setCount(count)
.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setResource(resource);
if (StringUtil.isNotBlank(limitApp)) {
rule.setLimitApp(limitApp);
}
FlowRuleManager.loadRules(Collections.singletonList(rule));
}
@Test
public void testCommonFilterMiscellaneous() throws Exception {
Constants.ROOT.removeChildList();
String url = "/hello";
this.mvc.perform(get(url))
.andExpect(status().isOk())
.andExpect(content().string(HELLO_STR));
ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
assertNotNull(cn);
assertEquals(1, cn.passQps(), 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);
testCommonBlockAndRedirectBlockPage(url, cn);
// Test for url cleaner.
testUrlCleaner();
testUrlExclusion();
testCustomOriginParser();
}
private void testCommonBlockAndRedirectBlockPage(String url, ClusterNode cn) throws Exception {
configureRulesFor(url, 0);
// The request will be blocked and response is default block message.
WebServletConfig.setBlockPageHttpStatus(HttpStatus.OK.value());
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG));
assertEquals(1, cn.blockQps(), 0.01);
WebServletConfig.setBlockPageHttpStatus(HttpStatus.TOO_MANY_REQUESTS.value());
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isTooManyRequests())
.andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG));
// Test for redirect.
String redirectUrl = "http://some-location.com";
WebServletConfig.setBlockPage(redirectUrl);
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN))
.andExpect(status().is3xxRedirection())
.andExpect(header().string("Location", redirectUrl + "?http_referer=http://localhost/hello"));
FlowRuleManager.loadRules(null);
WebServletConfig.setBlockPage("");
}
private void testUrlCleaner() throws Exception {
final String fooPrefix = "/foo/";
String url1 = fooPrefix + 1;
String url2 = fooPrefix + 2;
WebCallbackManager.setUrlCleaner(new UrlCleaner() {
@Override
public String clean(String originUrl) {
if (originUrl.startsWith(fooPrefix)) {
return "/foo/*";
}
return originUrl;
}
});
this.mvc.perform(get(url1).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string("Hello 1"));
this.mvc.perform(get(url2).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string("Hello 2"));
ClusterNode cn = ClusterBuilderSlot.getClusterNode(fooPrefix + "*");
assertEquals(2, cn.passQps(), 0.01);
assertNull(ClusterBuilderSlot.getClusterNode(url1));
assertNull(ClusterBuilderSlot.getClusterNode(url2));
WebCallbackManager.setUrlCleaner(new DefaultUrlCleaner());
}
private void testUrlExclusion() throws Exception {
final String excludePrefix = "/exclude/";
String url = excludePrefix + 1;
WebCallbackManager.setUrlCleaner(new UrlCleaner() {
@Override
public String clean(String originUrl) {
if(originUrl.startsWith(excludePrefix)) {
return "";
}
return originUrl;
}
});
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string("Exclude 1"));
assertNull(ClusterBuilderSlot.getClusterNode(url));
WebCallbackManager.setUrlCleaner(new DefaultUrlCleaner());
}
private void testCustomOriginParser() throws Exception {
String url = "/hello";
String limitOrigin = "userA";
final String headerName = "S-User";
configureRulesFor(url, 0, limitOrigin);
WebCallbackManager.setRequestOriginParser(new RequestOriginParser() {
@Override
public String parseOrigin(HttpServletRequest request) {
String origin = request.getHeader(headerName);
return origin != null ? origin : "";
}
});
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN).header(headerName, "userB"))
.andExpect(status().isOk())
.andExpect(content().string(HELLO_STR));
// This will be blocked.
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN).header(headerName, limitOrigin))
.andExpect(status().isTooManyRequests())
.andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG));
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string(HELLO_STR));
WebCallbackManager.setRequestOriginParser(null);
FlowRuleManager.loadRules(null);
}
@After
public void cleanUp() {
FlowRuleManager.loadRules(null);
ClusterBuilderSlot.resetClusterNodes();
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Eric Zhao
*/
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
registration.setName("sentinelFilter");
registration.setOrder(1);
return registration;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Eric Zhao
*/
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Eric Zhao
*/
@RestController
public class TestController {
@GetMapping("/hello")
public String apiHello() {
return "Hello!";
}
@GetMapping("/err")
public String apiError() {
return "Oops...";
}
@GetMapping("/foo/{id}")
public String apiFoo(@PathVariable("id") Long id) {
return "Hello " + id;
}
@GetMapping("/exclude/{id}")
public String apiExclude(@PathVariable("id") Long id) {
return "Exclude " + id;
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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.servletcontext;
import com.alibaba.csp.sentinel.Constants;
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 org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Collections;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author zhaoyuguang
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestContextApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class CommonFilterContextTest {
private static final String HELLO_STR = "Hello!";
@Autowired
private MockMvc mvc;
private void configureRulesFor(String resource, int count) {
configureRulesFor(resource, count, "default");
}
private void configureRulesFor(String resource, int count, String limitApp) {
FlowRule rule = new FlowRule()
.setCount(count)
.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setResource(resource);
if (StringUtil.isNotBlank(limitApp)) {
rule.setLimitApp(limitApp);
}
FlowRuleManager.loadRules(Collections.singletonList(rule));
}
@Test
public void testCommonFilterMiscellaneous() throws Exception {
String url = "/hello";
this.mvc.perform(get(url))
.andExpect(status().isOk())
.andExpect(content().string(HELLO_STR));
ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
assertNotNull(cn);
assertEquals(1, cn.passQps(), 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(url, context);
}
@After
public void cleanUp() {
FlowRuleManager.loadRules(null);
ClusterBuilderSlot.resetClusterNodes();
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.servletcontext;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author zhaoyuguang
*/
@Configuration
public class FilterContextConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
registration.setName("sentinelFilter");
registration.setOrder(1);
return registration;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servletcontext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author zhaoyuguang
*/
@SpringBootApplication
public class TestContextApplication {
public static void main(String[] args) {
SpringApplication.run(TestContextApplication.class, args);
}
}

View File

@@ -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.servletcontext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author zhaoyuguang
*/
@RestController
public class TestContextController {
@GetMapping("/hello")
public String apiHello() {
return "Hello!";
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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.servletmethod;
import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig;
import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Collections;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
/**
* @author zhaoyuguang
* @author Roger Law
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class CommonFilterMethodTest {
private static final String HELLO_STR = "Hello!";
private static final String HELLO_POST_STR = "Hello Post!";
private static final String GET = "GET";
private static final String POST = "POST";
private static final String COLON = ":";
@Autowired
private MockMvc mvc;
private void configureRulesFor(String resource, int count) {
configureRulesFor(resource, count, "default");
}
private void configureRulesFor(String resource, int count, String limitApp) {
FlowRule rule = new FlowRule()
.setCount(count)
.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setResource(resource);
if (StringUtil.isNotBlank(limitApp)) {
rule.setLimitApp(limitApp);
}
FlowRuleManager.loadRules(Collections.singletonList(rule));
}
@Test
public void testCommonFilterMiscellaneous() throws Exception {
String url = "/hello";
this.mvc.perform(get(url))
.andExpect(status().isOk())
.andExpect(content().string(HELLO_STR));
ClusterNode cnGet = ClusterBuilderSlot.getClusterNode(GET + COLON + url);
assertNotNull(cnGet);
assertEquals(1, cnGet.passQps(), 0.01);
ClusterNode cnPost = ClusterBuilderSlot.getClusterNode(POST + COLON + url);
assertNull(cnPost);
this.mvc.perform(post(url))
.andExpect(status().isOk())
.andExpect(content().string(HELLO_POST_STR));
cnPost = ClusterBuilderSlot.getClusterNode(POST + COLON + url);
assertNotNull(cnPost);
assertEquals(1, cnPost.passQps(), 0.01);
testCommonBlockAndRedirectBlockPage(url, cnGet, cnPost);
}
private void testCommonBlockAndRedirectBlockPage(String url, ClusterNode cnGet, ClusterNode cnPost) throws Exception {
configureRulesFor(GET + ":" + url, 0);
// The request will be blocked and response is default block message.
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isTooManyRequests())
.andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG));
assertEquals(1, cnGet.blockQps(), 0.01);
// Test for post pass
this.mvc.perform(post(url))
.andExpect(status().isOk())
.andExpect(content().string(HELLO_POST_STR));
assertEquals(2, cnPost.passQps(), 0.01);
FlowRuleManager.loadRules(null);
WebServletConfig.setBlockPage("");
}
@After
public void cleanUp() {
FlowRuleManager.loadRules(null);
ClusterBuilderSlot.resetClusterNodes();
}
}

View File

@@ -0,0 +1,25 @@
package com.alibaba.csp.sentinel.adapter.servletmethod;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: Roger Law
**/
@Configuration
public class FilterMethodConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter(CommonFilter.HTTP_METHOD_SPECIFY, "true");
registration.setName("sentinelFilter");
registration.setOrder(1);
return registration;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servletmethod;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Eric Zhao
*/
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servletmethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Roger Law
*/
@RestController
public class TestMethodController {
@GetMapping("/hello")
public String apiHello() {
return "Hello!";
}
@PostMapping("/hello")
public String apiHelloPost() {
return "Hello Post!";
}
}