zuul 异常处理及分析


1. 简单实现一个会抛异常的 ZuulFilter

public class TestZuulFilter extends ZuulFilter {

    @Override
    public String filterType() {    // 以 pre 为例
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() { // 优先级
        return FilterConstants.FORM_BODY_WRAPPER_FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() { // 是否过滤
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        System.out.println(1/0);    // 会抛出异常
    }
}
默认返回的异常信息内容及结构
{
    "timestamp": 1585974019634,
    "status": 500,
    "error": "Internal Server Error",
    "message": "pre:TestZuulFilter"
}

2. 异常处理:

@RestController
public class TestErrorController implements ErrorController {

    /**
     * zuul的异常处理
     *
     * @param request HTTP请求
     * @return API统一响应
     */
    @RequestMapping
    public ResponseEntity error(HttpServletRequest request, HttpServletResponse response) {
        Integer code = (Integer) request.getAttribute("javax.servlet.error.status_code");
        Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");

        String message = "服务器内部错误";

        if (exception instanceof ZuulException) {
            message = exception.getCause().getMessage();
        }

        response.setStatus(HttpStatus.OK.value());

        return ResponseUtil.otherResponse(code, message);
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }
}

返回结果:

{
    "status": 500,
    "message": "/ by zero",
    "data": {}
}

3. 通过源码分析为何这样处理

public class ZuulServlet extends HttpServlet {
    。。。 //省略无关代码
    
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        try {
            this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                this.preRoute(); // 执行"pre" 类型的filter   
            } catch (ZuulException var12) {
                this.error(var12);
                this.postRoute();
                return;
            }
        }
    }
}


public class FilterProcessor {
    。。。 //省略无关代码
    
    public void preRoute() throws ZuulException {
        try {
            this.runFilters("pre");
        } catch (ZuulException var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName());
        }
    }
}

首先请求会到 ZuulServlet ,然后调用 preRoute()方法执行 filterType为"pre" 类型的filter

public class FilterProcessor {
    。。。 //省略无关代码
    
    public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }

        boolean bResult = false;
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType); // 列出所有同类型的filter
        if (list != null) {
            for(int i = 0; i < list.size(); ++i) { // 轮训所有 filter 
                ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
                Object result = this.processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= (Boolean)result;
                }
            }
        }

        return bResult;
    }
    
    // 该函数的作用是列出所有的同一类型的filter,然后进行排序
    public List<ZuulFilter> getFiltersByType(String filterType) {
        List<ZuulFilter> list = (List)this.hashFiltersByType.get(filterType);
        if (list != null) {
            return list;
        } else {
            List<ZuulFilter> list = new ArrayList();
            Collection<ZuulFilter> filters = this.filterRegistry.getAllFilters();
            Iterator iterator = filters.iterator();

            while(iterator.hasNext()) {
                ZuulFilter filter = (ZuulFilter)iterator.next();
                if (filter.filterType().equals(filterType)) {
                    list.add(filter);
                }
            }

            Collections.sort(list); // 这里就是排序过程
            this.hashFiltersByType.putIfAbsent(filterType, list);
            return list;
        }
    }

sType 为定义 ZuulFilter 时的 filterType类型,runFilters方法会列出所有的 sType 类型的 filter,然后根据我们自定义的 filterOrder 进行排序,排序之后会依次执行各个filter的run方法获取执行结果,代码如下:

    public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
        try {
            ..... // 省略部分代码
            
            ZuulFilterResult result = filter.runFilter(); // filter执行run 方法的返回结果
            ExecutionStatus s = result.getStatus();
            execTime = System.currentTimeMillis() - ltime;
            switch(s) { // 获取filter的执行结果
            case FAILED:
                t = result.getException(); // 如果失败了,t 用于记录异常信息
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                break;
            case SUCCESS:
                o = result.getResult();
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                if (bDebug) {
                    Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
                    Debug.compareContextState(filterName, copy);
                }
            }

            if (t != null) {
                throw t; // 如果失败了,t不为null,直接将异常抛出
            } else {
                this.usageNotifier.notify(filter, s);
                return o;
            }
        } catch (Throwable var15) {  // 捕获异常继续上抛
            if (bDebug) {
                Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + var15.getMessage());
            }

            this.usageNotifier.notify(filter, ExecutionStatus.FAILED);
            if (var15 instanceof ZuulException) {  // 如果是ZuulException的子类,强转一下抛出
                throw (ZuulException)var15;
            } else { // 如果不是,则固定格式抛出异常
                ZuulException ex = new ZuulException(var15, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                throw ex;
            }
        }
    }
    

先runfilter() 看看结果是否正常,如果成功则 return 继续运行下一个filter,如果失败,则将异常抛出。注意,这里的异常如果不是ZuulException 会被定义为ZuulException然后上抛,抛到哪里进行处理呢?

public class ZuulServlet extends HttpServlet {
    。。。 //省略无关代码
    
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        try {
            this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                this.preRoute();    
            } catch (ZuulException var12) {  // 这里进行处理
                this.error(var12);  // 获取前面抛出的异常,然后对异常进行处理
                this.postRoute();
                return;
            }
        }
    }
}

异常在 ZuulServlet 中得到处理,zuul 支持定义错误filter的功能,对错误进行处理,原理和 proRoute()是一致的,当执行 this.error(); 会轮询所有 sType 为 error 的filter,其中 zuul 默认存在一个 SendErrorFilter 用于处理中间遇到的异常

public class SendErrorFilter extends ZuulFilter {
    private static final Log log = LogFactory.getLog(SendErrorFilter.class);
    protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";
    @Value("${error.path:/error}") // 这里定义错误跳转的路径
    private String errorPath;

    public SendErrorFilter() {
    }

    public String filterType() {
        return "error";
    }

    public int filterOrder() {
        return 0;
    }

    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        return ctx.getThrowable() != null && !ctx.getBoolean("sendErrorFilter.ran", false);
    }

    public Object run() {
        try {
            RequestContext ctx = RequestContext.getCurrentContext();
            SendErrorFilter.ExceptionHolder exception = this.findZuulException(ctx.getThrowable());
            HttpServletRequest request = ctx.getRequest();
            request.setAttribute("javax.servlet.error.status_code", exception.getStatusCode());
            log.warn("Error during filtering", exception.getThrowable());
            request.setAttribute("javax.servlet.error.exception", exception.getThrowable());
            if (StringUtils.hasText(exception.getErrorCause())) {
                request.setAttribute("javax.servlet.error.message", exception.getErrorCause());
            }

            RequestDispatcher dispatcher = request.getRequestDispatcher(this.errorPath);
            if (dispatcher != null) {
                ctx.set("sendErrorFilter.ran", true);
                if (!ctx.getResponse().isCommitted()) {
                    ctx.setResponseStatusCode(exception.getStatusCode());
                    dispatcher.forward(request, ctx.getResponse());
                }
            }
        } catch (Exception var5) {
            ReflectionUtils.rethrowRuntimeException(var5);
        }

        return null;
    }
    
    。。。 //省略无关代码
    

代码可以看出,如果出现异常,SendErrorFilter 会将状态码和message 写入到 request 的属性中,然后 dispatcher.forward 直接跳转到定义的地址(默认为 /error)。

4. 总结

综上所述,如果需要自定义异常捕获,需要:

  1. 从 request 获取状态码及异常信息
  2. 覆盖原有 /error controller 的处理或者修改配置forward到其他自定义controller


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM