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

开源地址

https://github.com/XiaoMi/mone

一、为什么需要用户自定义动态filter

在系列文章《如何设计filter链》里我们介绍了filter的设计思路以及它们是如何被加载串联为一条链路的。这些filter目前都是网关里内置的filter,比如我们默认支持日志filter,mock filter等。但在实际的使用中,很多情景用户是需要可以自定义过滤器以满足一些自己的功能要求。所以自定义过滤器就非常有必要了。

更进一步,动态加载这些自定义filter也是必须的,如果新增一个自定义filter就需要重启我们的网关集群来更新filter链路,很显然是不被接受的。

二、核心设计与实现

1、总体设计

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方法以获取这些能力。

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);
复制代码

Nacos nacos = this.getBean(Nacos.class);

举例:

NacosConfig nacosConfig = new NacosConfig();
nacosConfig.setDataId(configKey);
nacosConfig.setGroupId("DEFAULT_GROUP");
String config = nacos.getConfig(nacosConfig);
复制代码
//处理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()
复制代码
//返回HttpResponseUtils.create(),举例
return HttpResponseUtils.create(Result.fail(GeneralCodes.Forbidden, HttpResponseStatus.FORBIDDEN.reasonPhrase()));
复制代码
// envGroup值有3种:staging, online(线上外网), intranet(线上内网)
String env = filterContext.getAttachment("envGroup", "staging");
复制代码
//获取filter传递进来的参数
filterParams = this.getFilterParams(apiInfo);
 
//在实际调用下游之前的一些代码
//...省略代码...
 
//实际调用下游
next(context, invoker, apiInfo, request)
 
//在实际调用下游之后的一些代码
//...省略代码...
 
复制代码

3、动态加载自定义filter

在编写好自定义filter并上传审核完成后,控制台会广播通知gateway集群里的每个节点,有新的filter加入,是时候reload filterchain了。

在第一节RequestFilterChainreload方法基础上,我们加入加载自定义filter的逻辑吧。入口还是reload方法,它在获取用户定义的filter列表时,调用了FilterManagergetUserFilterList方法。热加载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);
        
        //...省略部分代码...  
    }
}
复制代码

FilterManagergetUserFilterList方法

(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启用吧。

展开阅读全文

页面更新:2024-05-04

标签:网关   动态   用户   天穹   集群   下游   逻辑   加载   定义   代码   方法   系列

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top