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