init
This commit is contained in:
66
sentinel/sentinel-adapter/sentinel-web-servlet/README.md
Normal file
66
sentinel/sentinel-adapter/sentinel-web-servlet/README.md
Normal 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`.
|
||||
50
sentinel/sentinel-adapter/sentinel-web-servlet/pom.xml
Normal file
50
sentinel/sentinel-adapter/sentinel-web-servlet/pom.xml
Normal 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>
|
||||
@@ -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 = "";
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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!";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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!";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user