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:
- org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter
- org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter
- com.xxx.filter.pre.PreRoutingFilter
- org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter
- org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter
- org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter
- org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter
- com.xxx.filter.pre.PreRoutingFilter
- com.xxx.filter.post.PostRoutingFilter
- org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
- com.xxx.filter.post.PostRoutingFilter
- 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
- 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
- This request can be routed to the aService component normally, and the RequestContext is cleared in Zuul Servlet after the request is completed.
- The request goes back to the first round of PostRouting filter, where you need to get the RequestContext Times NPE
- NPE exceptions are captured by Zuul Servlet and entered into SendErrorFilter
- SendErrorFilter sets properties to request, but the request is null, throwing NPE again
- Captured by Filter Processor, print Filter threw Exception: xxx
Solution
- Rewrite SimpleRouteLocator, comment out truncated zuul.servletPath
- 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:
- 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)
- filterOrder: Returns the order in which filters are executed, repeatable, and incremental
- 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; }