[Problem Analysis] Zuul Gateway Errors ZuulException: Filter threw Exception after it cannot find a route

Keywords: Java xml

background

Enable the @EnableZuulProxy annotation in a business component as a gateway to a unified docking third-party system, so that it has both business components and authentication functions for third-party systems, so URLs should be distinguished, zuul.servletPath and server.context-path cannot be the same.

Key configuration

# Business Request Entry
server.context-path=/
# Third Party System Entry
zuul.servletPath=/thd
zuul.routes.aService.path=/thd/aService/**
zuul.routes.aService.serviceId=aService
...

problem

Access/thd/aService/bbb 404

Analysis

When interruption points follow up in SimpleRouteLocator and find an adapted url, they remove the zuul.servletPath part of the URL to match the routing, and they are not configured to decide whether to remove it - -

    private String adjustPath(final String path) {
        String adjustedPath = path;

        if (RequestUtils.isDispatcherServletRequest()
                && StringUtils.hasText(this.dispatcherServletPath)) {
            if (!this.dispatcherServletPath.equals("/")) {
                adjustedPath = path.substring(this.dispatcherServletPath.length());
                log.debug("Stripped dispatcherServletPath");
            }
        }
        else if (RequestUtils.isZuulServletRequest()) {// Whether zuul requests
            if (StringUtils.hasText(this.zuulServletPath)
                    && !this.zuulServletPath.equals("/")) {// Does the url contain the zuul.servletPath section
                adjustedPath = path.substring(this.zuulServletPath.length());
                log.debug("Stripped zuulServletPath");
            }
        }
        else {
            // do nothing
        }

        log.debug("adjustedPath=" + adjustedPath);
        return adjustedPath;
    }

Since then, the matching is actually / aService/bbb, but the printed log is No route found for uri: /thd/aService/bbb, which is confusing.

Well, since you're going to get rid of thd, I'll have another thirdapi to visit / thd/thd/aService/bbb! Or make a mistake:

ERROR com.netflix.zuul.FilterProcessor [] - Filter threw Exception
com.netflix.zuul.exception.ZuulException: Filter threw Exception
Caused by: java.lang.NullPointerException: null
    at org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter.run(SendErrorFilter.java:76)

In line 76 of this SendErrorFilter, you just set properties to the request. Is the request null?

request.setAttribute("javax.servlet.error.status_code", exception.nStatusCode);

Look at the detailed log, or print No route found for uri, which is printed in PreDecoration Filter.
Take a look at the code and print out the first zuul.servletPath in the url and forward the request.

        else {
            log.warn("No route found for uri: " + requestURI);

            String fallBackUri = requestURI;
            String fallbackPrefix = this.dispatcherServletPath; // default fallback
                                                                // servlet is
                                                                // DispatcherServlet

            if (RequestUtils.isZuulServletRequest()) {// If it's a Zuul request
                // remove the Zuul servletPath from the requestUri
                log.debug("zuulServletPath=" + this.properties.getServletPath());
                // Remove the first zuul.servletPath from the url
                fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), "");
                log.debug("Replaced Zuul servlet path:" + fallBackUri);
            }
            else {
                // remove the DispatcherServlet servletPath from the requestUri
                log.debug("dispatcherServletPath=" + this.dispatcherServletPath);
                fallBackUri = fallBackUri.replaceFirst(this.dispatcherServletPath, "");
                log.debug("Replaced DispatcherServlet servlet path:" + fallBackUri);
            }
            if (!fallBackUri.startsWith("/")) {
                fallBackUri = "/" + fallBackUri;
            }
            String forwardURI = fallbackPrefix + fallBackUri;
            forwardURI = forwardURI.replaceAll("//", "/");
            // Set forwarding identity, forwarded by EndForward Filter later
            ctx.set(FORWARD_TO_KEY, forwardURI);
        }

Where else can this request be forwarded? Following the Zuul process, the filters that record the request go through are as follows:

  1. org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter
  2. org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter
  3. com.xxx.filter.pre.PreRoutingFilter
  4. org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter
  5. org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter
  6. org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter
  7. org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter
  8. com.xxx.filter.pre.PreRoutingFilter
  9. com.xxx.filter.post.PostRoutingFilter
  10. org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
  11. com.xxx.filter.post.PostRoutingFilter
  12. org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter

The path of com.xxx is a self-defined filter. It can be seen that the path is pre - > route - > pre - > post - > post - > error, after two complete request process. According to the ZuulServlet.service method, the first request (step 10) will be cleared after the online text, so the request will be null when the second pass through PostRouting Filter, and then enter SendErrorFilter, the above error will occur.

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

            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

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

Additionally, the request.getAttribute("javax.servlet.forward.request_uri") is used to determine whether the request is forwarded or not. It is found that:
request.getAttribute("javax.servlet.forward.request_uri") has a value when you enter PreRouting Filter for the second time and PostRouting Filter for the first time, indicating that the request is forwarded.

It can be ascertained that the reason for the problem is that the request was forwarded to itself.

To sum up, after visiting / thd/thd/aService/bbb

  1. After entering PreDecoration Filter for the first time, No route for uri, remove the first paragraph / thd and send it to Send Forward Filter for forwarding, that is, forwarding / thd/aService/bbb
  2. This request can be routed to the aService component normally, and the RequestContext is cleared in Zuul Servlet after the request is completed.
  3. The request goes back to the first round of PostRouting filter, where you need to get the RequestContext Times NPE
  4. NPE exceptions are captured by Zuul Servlet and entered into SendErrorFilter
  5. SendErrorFilter sets properties to request, but the request is null, throwing NPE again
  6. Captured by Filter Processor, print Filter threw Exception: xxx

Solution

  1. Rewrite SimpleRouteLocator, comment out truncated zuul.servletPath
  2. Add a custom ErrorFilter that swallows the exception and does not throw it back when the previous filter has an exception and the request is forwarded to it by itself.

Custom ZuulFilter

Customizing ZuulFilter requires only inheriting ZuulFilter and implementing several abstract methods:

  1. Filter Type: Returns the type of filter, namely pre,route,post,error,static, where the static type filter is used to return a fixed response (refer to Static Response Filter)
  2. filterOrder: Returns the order in which filters are executed, repeatable, and incremental
  3. ShouFilter: Execute this filter or not

Zuul Execution Process Analysis

The order of execution for different types of filters is as follows. The code refers to the ZuulServlet.service section above.

st=>start: Requests are distributed to ZuulServlet
op-init=>operation: Set up RequestContext
op-pre=>operation: pre Filter
op-route=>operation: route Filter
op-post=>operation: post Filter
op-error=>operation: error Filter
e=>end: Eliminate RequestContext
cond1=>condition: No abnormality
cond2=>condition: No abnormality
cond3=>condition: No abnormality
st->op-init->op-pre->cond1
cond1(no)->op-error
cond1(yes)->op-route->cond2
cond2(no)->op-error
cond2(yes)->op-post->cond3
cond3(no)->op-error
cond3(yes)->e
&```

//The execution process of each filter:
1. Different types of filters from ZuulServlet Entrance ZuulRunner Corresponding methods in
public void route() throws ZuulException {
    FilterProcessor.getInstance().route();
}
2. Get an instance of FilterProcessor and enter the runFilters method
public void route() throws ZuulException {
    try {
        runFilters("route");
    } catch (ZuulException e) {
        throw e;
    } catch (Throwable e) {
        throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
    }
}
3. Get an instance of FilterLoader and get a list of filters of this type
public Object runFilters(String sType) throws Throwable {
    if (RequestContext.getCurrentContext().debugRouting()) {
        Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
    }
    boolean bResult = false;
    // Get a list of filters of this type
    List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
    if (list != null) {
        for (int i = 0; i < list.size(); i++) {
            ZuulFilter zuulFilter = list.get(i);
            // Execution filter
            Object result = processZuulFilter(zuulFilter);
            if (result != null && result instanceof Boolean) {
                bResult |= ((Boolean) result);
            }
        }
    }
    return bResult;
}
4. Execute the filter in the template method of ZuulFilter and get the result, which is recorded in the filter Execution Summary
public ZuulFilterResult runFilter() {
    ZuulFilterResult zr = new ZuulFilterResult();
    if (!isFilterDisabled()) {
        if (shouldFilter()) {// Do you execute this filter?
            Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
            try {
                // Execute run methods and get results
                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;
}

Posted by FidelGonzales on Sun, 01 Sep 2019 21:17:02 -0700