008-Sentinel清洗RESTful的@PathVariable

栏目: 后端 · 发布时间: 5年前

内容简介:这是坚持技术写作计划(含翻译)的第8篇,定个小目标999,每周最少2篇。前段时间的文章多是运维方面的,最近放出一波后端相关的。最近开始使用Sentinel进行流量保护,但是默认的web servlet filter是拦截全部http请求。在传统的项目中问题不大。但是如果项目中用了Spring MVC,并且用了@PathVariable就尴尬了。

这是坚持技术写作计划(含翻译)的第8篇,定个小目标999,每周最少2篇。

前段时间的文章多是运维方面的,最近放出一波后端相关的。

背景

最近开始使用Sentinel进行流量保护,但是默认的web servlet filter是拦截全部http请求。在传统的项目中问题不大。但是如果项目中用了Spring MVC,并且用了@PathVariable就尴尬了。

比如 uri pattern是 /foo/{id} ,而从Sentinel监控看 /foo/1/foo/2 就是两个资源了,并且Sentinel最大支持6000个资源,再多就不生效了。

解决办法

官方给的方案是:UrlCleaner

WebCallbackManager.setUrlCleaner(new UrlCleaner() {
           @Override
           public String clean(String originUrl) {
               if (originUrl.startsWith(fooPrefix)) {
                   return "/foo/*";
               }
               return originUrl;
           }
       });

但是想想就吐, /v1/{foo}/{bar}/qux/{baz} 这种的来个20来个,截一个我看看。

AOP

换种思路,uri pattern难搞,用笨办法 aop总行吧?答案是可以的。

@Aspect
public class SentinelResourceAspect {
    @Pointcut("within(com.anjia.*.web.rest..*)")
    public void sentinelResourcePackagePointcut() {
        // 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;
        // 务必保证finally会被执行
        try {
          // 资源名可使用任意有业务语义的字符串
          // 注意此处只是类名#方法名,方法重载是合并的,如果需要进行区分,
          // 可以获取参数类型加入到资源名称上
          entry = SphU.entry(joinPoint.getSignature().getDeclaringTypeName()+
                             "#"+joinPoint.getSignature().getName());
          // 被保护的业务逻辑
          // do something...
        } catch (BlockException ex) {
          // 资源访问阻止,被限流或被降级
          // 进行相应的处理操作
        } finally {
          if (entry != null) {
            entry.exit();
          }
        }
        return result;
    }
}

拦截器

温习一下 Spring mvc的执行流程 doFilter -> doService -> dispatcher -> preHandle -> controller -> postHandle -> afterCompletion -> filterAfter

核心的是 String pattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); 但是是在dispatcher阶段才赋值的,所以在CommFilter是取不到的,所以导致使用官方的Filter是不行的。只能用拦截器

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;

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);
            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)) {
                return EMPTY_ORIGIN;
            }
        }
        return origin;
    }


    private static final String EMPTY_ORIGIN = "";
}
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 WebMvcConfigurationSupport {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SentinelHandlerInterceptor());
    }
}

参考资料

招聘小广告

山东济南的小伙伴欢迎投简历啊 加入我们 , 一起搞事情。

长期招聘,Java程序员,大数据工程师,运维工程师,前端工程师。

谢谢支持

008-Sentinel清洗RESTful的@PathVariable 支付宝

008-Sentinel清洗RESTful的@PathVariable 微信


以上所述就是小编给大家介绍的《008-Sentinel清洗RESTful的@PathVariable》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript高级程序设计(第3版)

JavaScript高级程序设计(第3版)

[美] Nicholas C. Zakas / 李松峰、曹力 / 人民邮电出版社 / 2012-3-29 / 99.00元

本书是JavaScript 超级畅销书的最新版。ECMAScript 5 和HTML5 在标准之争中双双胜出,使大量专有实现和客户端扩展正式进入规范,同时也为JavaScript 增添了很多适应未来发展的新特性。本书这一版除增加5 章全新内容外,其他章节也有较大幅度的增补和修订,新内容篇幅约占三分之一。全书从JavaScript 语言实现的各个组成部分——语言核心、DOM、BOM、事件模型讲起,深......一起来看看 《JavaScript高级程序设计(第3版)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器