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

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.