This is article 8 of the Stick to Technical Writing Project (including translation). Set a small goal of 999, at least 2 articles per week.
Some previous articles are mostly about operation and maintenance, and recently released a wave of back-end related.
background
Sentinel has recently been used for traffic protection, but the default Web Servlet Filter is to intercept all HTTP requests. It’s not a problem in a traditional project. However, if the project uses Spring MVC and uses @pathVariable, it will be awkward. For example, if the uri pattern is /foo/{id}, /foo/1 and /foo/2 are two resources, and Sentinel supports up to 6000 resources.
The solution
The official solution is :UrlCleaner
WebCallbackManager.setUrlCleaner(new UrlCleaner() {
@Override
public String clean(String originUrl) {
if (originUrl.startsWith(fooPrefix)) {
return "/foo/*";
}
returnoriginUrl; }});Copy the code
/v1/{foo}/{bar}/qux/{baz}
AOP
In other words, URI pattern is difficult to understand. The answer is yes.
@Aspect
public class SentinelResourceAspect {
@Pointcut("within(com.anjia.*.web.rest.. *)")
public void sentinelResourcePackagePointcut(a) {
// Method is empty as this is just a Pointcut, the implementations are
// in the advices.
}
@Around("sentinelResourcePackagePointcut()")
public Object sentinelResourceAround(ProceedingJoinPoint joinPoint) throws Throwable {
Entry entry = null;
// Ensure that finally is executed
try {
// The resource name can be any string with business semantics
// Note that this is only the class name # method name. Method overloads are merged.
// You can get the parameter type to add to the resource name
entry = SphU.entry(joinPoint.getSignature().getDeclaringTypeName()+
"#"+joinPoint.getSignature().getName());
// Protected business logic
// do something...
} catch (BlockException ex) {
// Resource access blocked, traffic restricted or degraded
// Perform the corresponding processing operation
} finally {
if(entry ! =null) { entry.exit(); }}returnresult; }}Copy the code
The interceptor
DoFilter -> doService -> Dispatcher -> preHandle -> Controller -> postHandle -> afterCompletion -> filterAfter
String pattern = (String) request.getAttribute(handlerMapping.best_matching_pattern_attribute); However, the value is assigned in the Dispatcher phase, so it is not available in CommFilter, so it is not possible to use the official Filter. Interceptors only
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
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.util.FilterUtil;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class SentinelHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String origin = parseOrigin(request);
String pattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
String uriTarget = StringUtils.defaultString(pattern,FilterUtil.filterTarget(request));
try {
// 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) {
uriTarget = urlCleaner.clean(uriTarget);
}
RecordLog.info(String.format("[Sentinel Pre Filter] Origin: %s enter Uri Path: %s", origin, uriTarget));
SphU.entry(uriTarget, EntryType.IN);
return true;
} catch (BlockException ex) {
RecordLog.warn(String.format("[Sentinel Pre Filter] Block Exception when Origin: %s enter fall back uri: %s", origin, uriTarget), ex);
WebCallbackManager.getUrlBlockHandler().blocked(request, response, ex);
return false; }}@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
while(ContextUtil.getContext() ! =null&& ContextUtil.getContext().getCurEntry() ! =null) {
ContextUtil.getContext().getCurEntry().exit();
}
ContextUtil.exit();
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}private String parseOrigin(HttpServletRequest request) {
RequestOriginParser originParser = WebCallbackManager.getRequestOriginParser();
String origin = EMPTY_ORIGIN;
if(originParser ! =null) {
origin = originParser.parseOrigin(request);
if (StringUtil.isEmpty(origin)) {
returnEMPTY_ORIGIN; }}return origin;
}
private static final String EMPTY_ORIGIN = "";
}
Copy the code
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Inject
SentinelHandlerInterceptor sentinelHandlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(sentinelHandlerInterceptor); }}Copy the code
UrlBlockHandler and UrlCleaner and WebServletConfig setBlockPage (blockPage)
As I said, UrlCleaner is for merging requests, for cleaning urls. UrlBlockHandler is the default handler after being intercepted. But clean and handler are not chained, so if you have multiple processes, you have to do your own logic in one method.
UrlCleaner
WebCallbackManager.setUrlCleaner(new UrlCleaner() {
@Override
public String clean(String originUrl) {
if (originUrl.startsWith(fooPrefix)) {
return "/foo/*";
}
returnoriginUrl; }});Copy the code
UrlBlockHandler, if generic, can adapt itself to the content-type of the request and return the content (PLAN_TEXT and JSON)
WebCallbackManager.setUrlBlockHandler((request, response, ex) -> { response.addHeader("Content-Type","application/json; charset=UTF-8"); PrintWriter out = response.getWriter(); Out. Print (" {\ "code \" MSG ": 429, \ '\" : \ "system is busy, please try again later \" "} "); out.flush(); out.close(); });Copy the code
WebServletConfig.setBlockPage(blockPage)
WebServletConfig.setBlockPage("http://www.baidu.com")
Copy the code
Note that none of the three methods does not support call chains. For example, I write two URlBlockHandlers and only recognize the last one.
The resources
- Adaptation of wiki/ mainstream frameworks # Web-Servlet
- issues#REST API Pattern UrlCleaner same processing
Want ads
Friends in Jinan, Shandong, welcome to join us and do things together.
Long-term recruitment, Java programmer, big data engineer, operation and maintenance engineer, front-end engineer.