范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

天穹gateway网关系列3用户自定义动态filter

  开源地址
  https://github.com/XiaoMi/mone一、为什么需要用户自定义动态filter
  在系列文章《如何设计filter链》里我们介绍了filter的设计思路以及它们是如何被加载串联为一条链路的。这些filter目前都是网关里内置的filter,比如我们默认支持日志filter,mock filter等。但在实际的使用中,很多情景用户是需要可以自定义过滤器以满足一些自己的功能要求。所以自定义过滤器就非常有必要了。
  更进一步,动态加载这些自定义filter也是必须的,如果新增一个自定义filter就需要重启我们的网关集群来更新filter链路,很显然是不被接受的。二、核心设计与实现1、总体设计
  添加自定义filter: 在网关控制台上传自定义filter,后台解析代码,分析出FilterDef(能唯一定义一个filter) 。生成一条filter的记录。编译自定义filter: 将上传的代码进行编译,并存储到文件服务器,方便gateway集群拉取到jar包。审核自定义filter: filter是用户自定义的,并且会被加载到网关集群,所以一定要review一下代码进行审核。启用自定义filter: 在上述步骤完成之后,就可以启用filter使其生效了。2、编写自定义filter
  所有用户自定义filter都需要实现抽象类CustomRequestFilter,CustomRequestFilter实现了RequestFilter。用户filter只需要实现CustomRequestFilter里的execute方法即可。public abstract class CustomRequestFilter extends RequestFilter {      @Override     public final FullHttpResponse doFilter(FilterContext context, Invoker invoker, ApiInfo apiInfo, FullHttpRequest request) {         if (this.allow(apiInfo)) {             try {                 context.setNext(false);                 return execute(context, invoker, apiInfo, request);             } catch (Throwable ex) {                 log.error("invoke custom filter:{} error:{}", this.getDef().getName(), ex.getMessage());                 //filter chain 已经执行过了,不再第二次执行了                 if (!context.isNext()) {                     return invoker.doInvoker(context, apiInfo, request);                 }                 return HttpResponseUtils.create(Result.fromException(ex));             }         } else {             return invoker.doInvoker(context, apiInfo, request);         }     }      public FullHttpResponse next(FilterContext context, Invoker invoker, ApiInfo apiInfo, FullHttpRequest request) {         context.setNext(true);         return invoker.doInvoker(context, apiInfo, request);     }      public abstract FullHttpResponse execute(FilterContext context, Invoker invoker, ApiInfo apiInfo, FullHttpRequest request); } 复制代码
  下图是一个实际的filter例子,可以看到处理逻辑都写到了execute方法里面。resources里面的FilterDef唯一定义该filter。
  在实际编写自定义filter时,用户可能会用到更加丰富的能力,比如使用调用一个rpc接口,获取一个动态配置等,为此我们在RequestFilter里提供了getBean方法以获取这些能力。在filter中引入dubbo
  Dubbo dubbo = this.getBean(Dubbo.class);
  举例:MethodInfo methodInfo = new MethodInfo(); methodInfo.setServiceName("com.xiaomi.planet.user.module.api.service.SpecialUserService"); methodInfo.setMethodName("testMethod"); methodInfo.setGroup("staging"); methodInfo.setVersion("1.0"); methodInfo.setParameterTypes(new String[] { "java.lang.Integer", "java.lang.Integer" }); methodInfo.setArgs(new Object[] { 1, 1 }); Object result = dubbo.call(methodInfo); 复制代码在filter中引入 nacos
  Nacos nacos = this.getBean(Nacos.class);
  举例:NacosConfig nacosConfig = new NacosConfig(); nacosConfig.setDataId(configKey); nacosConfig.setGroupId("DEFAULT_GROUP"); String config = nacos.getConfig(nacosConfig); 复制代码在filter中获取请求参数和header//处理get Map queryParams = HttpRequestUtils.getQueryParams(request.uri());   //处理post String postStr = new String(HttpRequestUtils.getRequestBody(request));   //处理表单 HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), request); List postData = decoder.getBodyHttpDatas(); for (InterfaceHttpData data : postData) {     if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {         MemoryAttribute attribute = (MemoryAttribute) data;         kv.put(attribute.getName(), attribute.getValue());     } }   //处理header FullHttpRequest request.headers() 复制代码在filter里返回自定义结果//返回HttpResponseUtils.create(),举例 return HttpResponseUtils.create(Result.fail(GeneralCodes.Forbidden, HttpResponseStatus.FORBIDDEN.reasonPhrase())); 复制代码在filter里区分环境// envGroup值有3种:staging, online(线上外网), intranet(线上内网) String env = filterContext.getAttachment("envGroup", "staging"); 复制代码其他filter里的一些处理//获取filter传递进来的参数 filterParams = this.getFilterParams(apiInfo);   //在实际调用下游之前的一些代码 //...省略代码...   //实际调用下游 next(context, invoker, apiInfo, request)   //在实际调用下游之后的一些代码 //...省略代码...   复制代码3、动态加载自定义filter
  在编写好自定义filter并上传审核完成后,控制台会广播通知gateway集群里的每个节点,有新的filter加入,是时候reload filterchain了。
  在第一节RequestFilterChain的reload方法基础上,我们加入加载自定义filter的逻辑吧。入口还是reload方法,它在获取用户定义的filter列表时,调用了FilterManager的getUserFilterList方法。热加载filter的逻辑我们都写到了FilterManager里面。@Slf4j @Component public class RequestFilterChain implements IRequestFilterChain {      @Autowired     private ApplicationContext ac;      @Autowired     private FilterManager filterManager;      private final CopyOnWriteArrayList filterList = new CopyOnWriteArrayList<>();      //加载filter public void reload(String type, List names) {         log.info("reload filter");         //获取系统定义的filter         Map map = ac.getBeansOfType(RequestFilter.class);         List list = new ArrayList<>(map.values());         log.info("system filter size:{}", list.size());         //获取用户定义的filter         List userFilterList = filterManager.getUserFilterList(type, names).stream()                 .filter(it -> filterUserFilterWithGroup(it)).collect(Collectors.toList());         log.info("user filter size:{} type:{} names:{}", userFilterList.size(), type, names);          list.addAll(userFilterList);         list = sortFilterList(list);                  //...省略部分代码...       } } 复制代码
  FilterManager的getUserFilterList方法
  (getUserFilterList -> loadRequestFilter -> loadFilter)//省略一部分代码,可前往https://github.com/XiaoMi/mone/tree/master/gateway-all查看 public class FilterManager {     public List getUserFilterList(String type, List names) {         try {             if (!configService.isAllowUserFilter()) {                 log.info("skip user filter");                 return Lists.newArrayList();             }             //将老的filter jar包删除             deleteOldFilter(type, names);             //从文件中心将编译好的filter jar包下载到本地             downloadFilter(type, names);             List jarList = getJarPathList();             log.info("jarList:{}", jarList);             //热加载filter             return loadRequestFilter(jarList);         } catch (Throwable ex) {             log.error("getUserFilterList ex:{}", ex.getMessage());             return Lists.newArrayList();         }     }             public List loadRequestFilter(List pathNameList) {         if (pathNameList.size() == 0) {             return Lists.newArrayList();         }         try {             URL[] urls = pathNameList.stream().map(p -> {                 try {                     return new URL("file:" + p);                 } catch (MalformedURLException e) {                     log.error(e.getMessage());                 }                 return null;             }).filter(it -> null != it).toArray(URL[]::new);             return Arrays.stream(urls).map(url -> {                 try {                     log.info("load request filter url:{}", url);                     URLClassLoader classLoader = new URLClassLoader(new URL[]{url});                     return loadFilter(url.getFile(), classLoader);                 } catch (Throwable e) {                     log.error("load filter error, url: {}, msg: {}", url, e.getMessage(), e);                 }                 return null;             }).filter(it -> null != it).collect(Collectors.toList());          } catch (Throwable ex) {             log.error(ex.getMessage(), ex);         }         return Lists.newArrayList();     }          public RequestFilter loadFilter(String url, URLClassLoader classLoader) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {         String content = ZipUtils.readFile(url, "FilterDef");         Properties properties = new Properties();         properties.load(new StringInputStream(content));         String filterClass = properties.getProperty("filter");         Class<?> clazz = classLoader.loadClass(filterClass);         RequestFilter ins = (RequestFilter) clazz.newInstance();         String name = properties.getProperty("name");         String author = properties.getProperty("author");         String groups = properties.getProperty("groups");         log.info("loadFilter, name:{}, author:{}, groups:{} ", name, author, groups);         classLoaderMap.put(name, classLoader);         ins.setDef(new FilterDef(0, name, author, groups));         ins.setGetBeanFunction(getBean());         return ins;     } } 复制代码
  至此,用户可以随时增加一个新的gateway filter,或者更新那些已经存在的filter,而不用进行任何重启。4、使用业务自定义filter
  在添加实际的apiinfo接口时,选择适合你接口的filter启用吧。

吉利帝豪L雷神Hi。X和比亚迪秦PLUSDM有蜗友问纠结于吉利帝豪L雷神Hi。X和比亚迪秦PLUSDMi,谁更值得买?会在这两款车间纠结,说明你要么是比较懂车的车迷或老司机,要么就是功课做得很足。因为它俩无论从价位级别驱动方马来西亚预言家拿督郑博见,对2022年做出10大预言,真的可信吗?提到预言,不知道大家知不知道,近代命中预言最多的人是谁?我想很多人会说是拿督郑博见,对!没错,之所以这么称呼他,这是因为他在对2020年的14条预言中,竟然命中了12条。这也让拿督日本富豪榜TOP50孙正义痛失首富宝座,日本马云无缘前五2022年6月初美国商业杂志福布斯发布2022年日本富豪榜。进入榜单50位富豪总财富约1700亿美元(折合人民币11322亿元),与上年相比缩水近三分之一。总体来看,今年榜单有3位云上律说看完本文,你能比很多律师都懂怎么申请行政复议(下)上次我们说到申请行政复议的前期准备工作云上律说看完本文,你能比很多律师都懂怎么申请行政复议(上)本文我们继续聊五向哪个部门申请行政复议材料都准备好,这些材料应该交到哪里去,视不同情每天学习一味中药补虚药之补血药补虚药凡以补虚扶弱,纠正人体气血阴阳的不足为主要功效,常用以治疗虚证的药物,称为补虚药,也称补益药或补养药。根据补虚药在性能功效及主治方面的不同,一般又分为补气药补阳河南女大学脑出血被延误救治,这八点你应该知道先和大家分享几个和脑出血有关的故事。当然对我们来说是故事,对当事人来说却是事故了。这里指的是自发性脑出血,而非外伤性脑出血。某年冬天,除夕前夜,急诊室里来了一位三十多岁的厨师,自诉养个大学生,就像发射一颗卫星,生动比喻讨要生活费的大学生大学是人生当中最幸福的一个阶段了,学习没有高中那么累,课也比高中少,还能谈恋爱,没有老师和家长管着生活自由了好多,要说唯一的缺点应该就是经济不独立。大学生只能通过做点兼职挣钱,大部3年美国高中花110万,现在没钱了想募捐,坚决不回国上大学近日,看到一条上电视为留学美国的女儿呼吁捐款的消息,觉得挺有意思。在一些报道中称这个母亲是给在美国上大学的女儿呼吁捐款,可是经反复查看,发现并不是那个样子。这个母亲姓李,湖南(应当致敬英雄,如果杨勇最后没有拉制动杆,列车将面临什么后果?致敬英雄记住他,D2809殉职司机叫杨勇,在生命的最后5秒钟,他的最后一次操作,却救下了整量列车上的乘客,有网友有疑问,为何那个地方会突然发生泥石流坍塌,倘若没有杨勇的这一操作的话无题无题佛日众生皆苦,上帝说来到这个世界的每个人都是罪人,需要经历这样那样的痛苦和磨难,很多人活到生命最后一刻也没活明白。人生皆是借假修真,寂静空无。男人与女人最根本的需求区别如下男人的痛大年初六,六六大顺,愿我最爱的人,万事顺心郝有花(图片来自网络)一段过往,一抹遗憾在时间的脚步声里悄悄画上句号从此,陌生依然带着一份相伴的暖意在年外等,在年里盼电话安静地像一把锁子锁住守候,不问归期枝头的喜鹊叽叽歪歪叫个不
你遇到过年龄小但是辈分高的事情吗?我遇见过一个笑死人的事。我有两程姓同学结夫妻,他们村(镇上)两人千多口,有一多半姓程一家族。女同学辈分高,论辈婆婆得叫她姑奶,是独生女(老爸五六十捡的)。老爸是抗伤残老兵一等功臣参退休后做什么事情最让人看不起?我认为退休后做两件事情最让人瞧不起。一是有了老年证后,不管是否坐车高峰期,倚老卖老,让年轻人让座位,还不道谢,在车上高声说话,不顾及他人感受。二是贪图小便宜,买菜时把叶菜扒的干干净有人说黄河水底有很多宝贝,是真的吗?黄河流域一直有很多传说,其中就有说黄河里有数不尽的宝贝,自古不知道多少英雄好汉冒死寻宝,在黄河里探索甚至潜入黄河底打捞,又说得了多少宝贝。至于真假,众说纷纭,反正无风不起浪,黄河从家里人出首付在西安买了套房,每月还款3900,我每月工资就4000,怎么办?2016年7月我大专毕业了,2017年12月家里拿首付给我买了套房,买房之前我的月薪是5500,而且实际我是2016年3月就上班了,可是16年整年都混混沌沌,工作一直不顺利,我换了武汉月薪18。4k是什么水平?武汉市的月薪分五档。一,最高档,富有阶层,12000元以上。占比,百分之五。二,中高档,中产阶层,900012000元。占比,百分之二十。三,中挡,小康阶层,60009000元。占湖北文理学院能升为重点大学吗?湖北文理学院(HubeiUniversityofArtsandScience)位于山水名城湖北省域副中心城市襄阳市,是省属本科院校,入选湖北省2011计划牵头高校。学校的前身是创办如果你有两个儿子,一个过得好很有钱,另一个很穷,你会让有钱的那个帮穷的那个吗?我叔家很有钱,我们家相对来说就很穷。因为一些原因,我妈和我婶子将近二十年没有说过一句话,可在我们家需要钱的时候,我叔和我婶子还是二话不说借给我们十五万块钱,即便如此,我妈还是不搭理部队突围时伤兵怎么安置?这种要分具体情况,就拿你死我活的抗日战争来吧国军部队在极端严峻条件下突围时,无论是野外突围还是向城外突围,重伤员基本都是丢弃在战场上的。这些可怜的重伤员的命运可想而知,往往会死在日昆工和重邮的综合实力都比成都理工强,为什么后者却是双一流?成都理工有二个国家重点实验室,前二年获得多项国家级大奖。昆工和重邮有几个?科研成果方面和成都理工不是一个档次的,成都理工大学为什么会被评为一流学科高校这个问题我觉得是教育界的一个迷活在当今社会是什么感觉?只有上世纪五六七十年代的人才知道,今天的幸福生活来之不易!要比当年和现在无法比!要凭感觉,没话说!物质丰富了,但精神疲惫了。活在当今,能挣大钱人开心,挣小钱养家糊口人苦闷,生活水平在乡镇当公务员,每个月4000多元,该不该辞职?对比一下本地乡镇公务员工资水平,你再考虑要不要辞职笑哭笑哭。科员工资,除去五险一金,一个月2500左右,把车补乡镇补贴等所有补贴都加上,也就3700块钱左右,跟你的4000多还差了