博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
008-Sentinel清洗RESTful的@PathVariable
阅读量:6854 次
发布时间:2019-06-26

本文共 6767 字,大约阅读时间需要 22 分钟。

这是坚持技术写作计划(含翻译)的第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总行吧?答案是可以的。

@Aspectpublic 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;@Componentpublic 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)) {                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;@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter {    @Inject    SentinelHandlerInterceptor sentinelHandlerInterceptor;    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(sentinelHandlerInterceptor);    }}复制代码

UrlBlockHandler和UrlCleaner和WebServletConfig.setBlockPage(blockPage)

上面说过,UrlCleaner是为了归并请求,清洗url用的。而UrlBlockHandler是在被拦截后的默认处理器。但是clean和handler都不是链式的,所以如果有多种处理,需要自己在一个方法里,进行逻辑判断。

UrlCleaner

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

UrlBlockHandler

如果通用一点的,可以自己根据request的 content-type进行自适应返回内容(PLAN_TEXT和JSON)

WebCallbackManager.setUrlBlockHandler((request, response, ex) -> {    response.addHeader("Content-Type","application/json;charset=UTF-8");    PrintWriter out = response.getWriter();    out.print("{\"code\"":429,\"msg\":\"系统繁忙,请稍后重试\""}");    out.flush();    out.close();});复制代码

WebServletConfig.setBlockPage(blockPage)

WebServletConfig.setBlockPage("http://www.baidu.com")复制代码

注意,三个方法都不是不支持调用链,比如我写两个UrlBlockHandler,只认最后一个。

参考资料

招聘小广告

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

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

转载于:https://juejin.im/post/5c7f3692f265da2d8b636700

你可能感兴趣的文章
DVWA系列之4 利用SQLMap进行medium级别注入
查看>>
Powershell环境变量
查看>>
ruby 集合
查看>>
harbor进程组件化运行及systemd 进程日志分写
查看>>
Web自动化测试中使用groovy实现页面的对象化
查看>>
RHEL5基于RSA的公匙和私匙加密认证SSH应用于服务器远程备份
查看>>
让你成功安装vscode中go的相关插件
查看>>
Java仿雷电及其源代码
查看>>
linux关机命令
查看>>
Visual studio Express 2012 for Web 试用
查看>>
ruby初级语法知识
查看>>
CA证书服务器(1) 数据加密技术
查看>>
Qt学习之路(23): 自定义事件
查看>>
如何让Windows 2003更加安全
查看>>
烂泥:使用Navicat for SQL Server新建数据库、用户及权限赋予
查看>>
采用hadoop对日志进行分布式分析框架
查看>>
服务器监控和虚拟机管理之六PRO的配置与实现
查看>>
【转】烂泥:查看MySql版本号命令
查看>>
MFC绘制直方图和饼图
查看>>
tf.minimize
查看>>