F a Q Break Filter

如何中断 Zuul 过滤流程

ZuulServletFilterZuulServlet 可以看出 Zuul 的整体执行流程

ZuulServlet

Zuul 官方示例 和 Spring Cloud Zuul 都是通过 Servlet 的形式而不是 Servlet Filter 的形式进行启动的,所以一下以 Servlet 的流程举例:

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    try {
        init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

        RequestContext context = RequestContext.getCurrentContext();
        // 把这个请求标记为 Zuul 引擎处理
        context.setZuulEngineRan();

        try {
            // per
            preRoute();
        } catch (ZuulException e) {
            error(e); // errooooooooooooor
            postRoute(); // !!!
            return;
        }
        try {
            // route
            route();
        } catch (ZuulException e) {
            error(e); // errooooooooooooor
            postRoute(); // !!!
            return;
        }
        try {
            // post
            postRoute();
        } catch (ZuulException e) {
            error(e); // errooooooooooooor
            return;
        }

    } catch (Throwable e) {
        // errooooooooooooor
        error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
    } finally {
        RequestContext.getCurrentContext().unset();
    }
}

Zuul 过滤器的执行流程

FilterProcessor # runFilters

public Object runFilters(String sType) throws Throwable {
    ...
    // 从 Zuul FilterRegistry 中获取所有指定类型的过滤器,按照 filterOrder 正序排序
    List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
    if (list != null) {
        for (int i = 0; i < list.size(); i++) {
            ZuulFilter zuulFilter = list.get(i);
            // FilterProcessor # processZuulFilter
            // 遍历指定类型的过滤器(如:所有的 per) 一个一个执行
            Object result = processZuulFilter(zuulFilter);
            if (result != null && result instanceof Boolean) {
                bResult |= ((Boolean) result);
            }
        }
    }
    return bResult;
}

FilterProcessor # processZuulFilter

public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
	...
    try {
        Throwable t = null;
        ...
        // ZuulFilter # runFilter
        ZuulFilterResult result = filter.runFilter();
        ExecutionStatus s = result.getStatus();
        ...
        // 根据 过滤器的执行状态,记录概要到上下文
        switch (s) {
            case FAILED:
                // 如果执行失败,保存异常对象
                t = result.getException();
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                break;
            case SUCCESS:
                o = result.getResult();
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                ...
                break;
            default:
                break;
        }
	
        // 如果有异常 抛出异样
        if (t != null) throw t;

        usageNotifier.notify(filter, s);
        return o;

    } catch (Throwable e) {
        // 把异常包装成 ZuulException 抛出
        ...
    }
}

ZuulFilter # runFilter

public ZuulFilterResult runFilter() {
    ZuulFilterResult zr = new ZuulFilterResult();
    // Filter 不可用,直接返回 zr
    if (!isFilterDisabled()) {
        // Filter 不应该执行,标记 状态为 SKIPPED 然后返回
        if (shouldFilter()) {
            Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
            // 执行 Filter run() 方法,根据是否抛出异常,标记为成功或者失败状态
            try {
                Object res = run();
                // 标记成功状态
                zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
            } catch (Throwable e) {
                t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                // 标记失败状态
                zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                zr.setException(e);
            } finally {
                t.stopAndLog();
            }
        } else {
            zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
        }
    }
    return zr;
}

抛异常

从上面 Zuul 的执行流程可以看出,抛异常是可以终止 同一个类型 的过滤器执行的,因为在 FilterProcessor # runFilters 循环执行某个指定类型的过滤器的时候,并没有捕捉异常,这时候如果有异常,循环就直接终止了。

ctx.setSendZuulResponse(false);

但是从 Zuul Filter 的整个生命周期(ZuulServlet)来看,在 preroute 这两个阶段 抛异常, post 类型的过滤器,仍然会执行。从 ZuulFilter # runFilter 中可以看出,除了抛异常外,shouldFilter() 返回 false 过滤器也是不执行的。

查看 Spring Cloud 内置 RibbonRoutingFilterSimpleHostRoutingFiltershouldFilter() 方法,里面都有一段这样的逻辑 (...) && ctx.sendZuulResponse() , 所以把 sendZuulResponse 标记为 false,就不会转发到 Origin 了。

总结

我们写自己的过滤器的时候,最常实现的过滤器类型是 prepostpre 常用来用来进行权限校验等,如果校验失败,并且 post 写的不好的, 抛异常设置ctx.setSendZuulResponse(false) 都是无法阻止 post 型过滤器执行的。

  • 一定要注意 过滤器的执行顺序,留意是在内置过滤器之前还是之后执行
  • shouldFilter() 的实现,粒度一定经要细,建议 都加上 (...) && ctx.sendZuulResponse() 的逻辑,同时抛出异常,终止内置过滤器 和 自定义过滤器 的执行
  • 如果要重定向 ,进行以下设置,这时候 内置过滤器 LocationRewriteFilter 会进行处理
    • ctx.setSendZuulResponse(false);
    • ctx.setResponseStatusCode(301);
    • ctx.addZuulResponseHeader("Location", "xxx");

突然感觉 Netflix Zuul 其实是只是一个 框架,还不算一个真正意义上的网关,zuul-core 并没有实现具体的Filter逻辑;Spring Cloud Zuul 内置了一些的 Filter 实现,具有的基础的代理、转发、重定向 等简单的功能;但是如果要作为一个真正的网关,如统一权限校验、流量控制、缓存、监控等功能,都需要我们自己实现。