-
-
Save espartero/d38e64847d584775f708086b85cef2d2 to your computer and use it in GitHub Desktop.
Attempt to fix Netflix Zuul - NoSuchMethodError: ErrorController.getErrorPath()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.eurecat.xcare.core.gateway.spring.config; | |
import com.netflix.zuul.context.RequestContext; | |
import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator; | |
import org.springframework.cloud.netflix.zuul.filters.Route; | |
import org.springframework.cloud.netflix.zuul.filters.RouteLocator; | |
import org.springframework.cloud.netflix.zuul.web.ZuulController; | |
import org.springframework.util.AntPathMatcher; | |
import org.springframework.util.PathMatcher; | |
import org.springframework.web.cors.CorsConfiguration; | |
import org.springframework.web.servlet.HandlerExecutionChain; | |
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; | |
import javax.servlet.http.HttpServletRequest; | |
import java.util.Collection; | |
public class Spring5ZuulHandlerMapping extends AbstractUrlHandlerMapping { | |
private final RouteLocator routeLocator; | |
private final ZuulController zuul; | |
private final String errorPath; | |
private PathMatcher pathMatcher = new AntPathMatcher(); | |
private volatile boolean dirty = true; | |
public Spring5ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul, String errorPath) { | |
this.routeLocator = routeLocator; | |
this.zuul = zuul; | |
this.errorPath = errorPath; | |
} | |
@Override | |
protected HandlerExecutionChain getCorsHandlerExecutionChain( | |
HttpServletRequest request, HandlerExecutionChain chain, | |
CorsConfiguration config) { | |
if (config == null) { | |
// Allow CORS requests to go to the backend | |
return chain; | |
} | |
return super.getCorsHandlerExecutionChain(request, chain, config); | |
} | |
public void setDirty(boolean dirty) { | |
this.dirty = dirty; | |
if (this.routeLocator instanceof RefreshableRouteLocator) { | |
((RefreshableRouteLocator) this.routeLocator).refresh(); | |
} | |
} | |
@Override | |
protected Object lookupHandler(String urlPath, HttpServletRequest request) | |
throws Exception { | |
if (urlPath.equals(this.errorPath)) { | |
return null; | |
} | |
if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) { | |
return null; | |
} | |
RequestContext ctx = RequestContext.getCurrentContext(); | |
if (ctx.containsKey("forward.to")) { | |
return null; | |
} | |
if (this.dirty) { | |
synchronized (this) { | |
if (this.dirty) { | |
registerHandlers(); | |
this.dirty = false; | |
} | |
} | |
} | |
return super.lookupHandler(urlPath, request); | |
} | |
private boolean isIgnoredPath(String urlPath, Collection<String> ignored) { | |
if (ignored != null) { | |
for (String ignoredPath : ignored) { | |
if (this.pathMatcher.match(ignoredPath, urlPath)) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
private void registerHandlers() { | |
Collection<Route> routes = this.routeLocator.getRoutes(); | |
if (routes.isEmpty()) { | |
this.logger.warn("No routes found from RouteLocator"); | |
} else { | |
for (Route route : routes) { | |
registerHandler(route.getFullPath(), this.zuul); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.eurecat.xcare.core.gateway.spring.config; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.beans.factory.annotation.Value; | |
import org.springframework.cloud.client.discovery.event.HeartbeatEvent; | |
import org.springframework.cloud.client.discovery.event.HeartbeatMonitor; | |
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; | |
import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent; | |
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent; | |
import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent; | |
import org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration; | |
import org.springframework.cloud.netflix.zuul.filters.RouteLocator; | |
import org.springframework.cloud.netflix.zuul.web.ZuulController; | |
import org.springframework.context.ApplicationEvent; | |
import org.springframework.context.ApplicationListener; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.context.event.ContextRefreshedEvent; | |
import org.springframework.web.cors.CorsConfiguration; | |
import org.springframework.web.servlet.DispatcherServlet; | |
import org.springframework.web.servlet.config.annotation.CorsRegistry; | |
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | |
import java.util.List; | |
import java.util.Map; | |
import static java.util.Collections.emptyList; | |
@Configuration | |
public class ZuulConfiguration { | |
private Map<String, CorsConfiguration> corsConfigurations; | |
@Autowired(required = false) | |
private List<WebMvcConfigurer> configurers = emptyList(); | |
// Extracted from org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController | |
@Value("${server.error.path:${error.path:/error}}") | |
private String errorPath; | |
@Bean | |
public Spring5ZuulHandlerMapping spring5ZuulHandlerMapping(RouteLocator routes, ZuulController zuulController) { | |
Spring5ZuulHandlerMapping mapping = new Spring5ZuulHandlerMapping(routes, zuulController, this.errorPath); | |
mapping.setCorsConfigurations(getCorsConfigurations()); | |
return mapping; | |
} | |
@Bean(name = DispatcherServlet.HANDLER_MAPPING_BEAN_NAME) | |
public ZuulExcludedCompositeHandlerMapping zuulExcludedCompositeHandlerMapping(DispatcherServlet dispatcherServlet) { | |
// Improve this!!! | |
dispatcherServlet.setDetectAllHandlerMappings(false); | |
return new ZuulExcludedCompositeHandlerMapping(); | |
} | |
@Bean | |
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() { | |
return new Spring5ZuulRefreshListener(); | |
} | |
protected final Map<String, CorsConfiguration> getCorsConfigurations() { | |
if (this.corsConfigurations == null) { | |
ZuulCorsRegistry registry = new ZuulCorsRegistry(); | |
this.configurers.forEach(configurer -> configurer.addCorsMappings(registry)); | |
this.corsConfigurations = registry.getCorsConfigurations(); | |
} | |
return this.corsConfigurations; | |
} | |
private static class ZuulCorsRegistry extends CorsRegistry { | |
@Override | |
protected Map<String, CorsConfiguration> getCorsConfigurations() { | |
return super.getCorsConfigurations(); | |
} | |
} | |
private static class Spring5ZuulRefreshListener implements ApplicationListener<ApplicationEvent> { | |
@Autowired | |
private Spring5ZuulHandlerMapping zuulHandlerMapping; | |
private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor(); | |
@Override | |
public void onApplicationEvent(ApplicationEvent event) { | |
if (event instanceof ContextRefreshedEvent | |
|| event instanceof RefreshScopeRefreshedEvent | |
|| event instanceof RoutesRefreshedEvent | |
|| event instanceof InstanceRegisteredEvent) { | |
reset(); | |
} else if (event instanceof ParentHeartbeatEvent) { | |
ParentHeartbeatEvent e = (ParentHeartbeatEvent) event; | |
resetIfNeeded(e.getValue()); | |
} else if (event instanceof HeartbeatEvent) { | |
HeartbeatEvent e = (HeartbeatEvent) event; | |
resetIfNeeded(e.getValue()); | |
} | |
} | |
private void resetIfNeeded(Object value) { | |
if (this.heartbeatMonitor.update(value)) { | |
reset(); | |
} | |
} | |
private void reset() { | |
this.zuulHandlerMapping.setDirty(true); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.eurecat.xcare.core.gateway.spring.config; | |
import org.springframework.beans.factory.ListableBeanFactory; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping; | |
import org.springframework.core.annotation.AnnotationAwareOrderComparator; | |
import org.springframework.web.servlet.HandlerExecutionChain; | |
import org.springframework.web.servlet.HandlerMapping; | |
import javax.servlet.http.HttpServletRequest; | |
import java.util.ArrayList; | |
import java.util.List; | |
/** | |
* Same that {@link org.springframework.boot.actuate.autoconfigure.web.servlet.CompositeHandlerMapping} but excluding {@link ZuulHandlerMapping}. | |
*/ | |
public class ZuulExcludedCompositeHandlerMapping implements HandlerMapping { | |
@Autowired | |
private ListableBeanFactory beanFactory; | |
private List<HandlerMapping> mappings; | |
@Override | |
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { | |
for (HandlerMapping mapping : getMappings()) { | |
HandlerExecutionChain handler = mapping.getHandler(request); | |
if (handler != null) { | |
return handler; | |
} | |
} | |
return null; | |
} | |
@Override | |
public boolean usesPathPatterns() { | |
for (HandlerMapping mapping : getMappings()) { | |
if (mapping.usesPathPatterns()) { | |
return true; | |
} | |
} | |
return false; | |
} | |
private List<HandlerMapping> getMappings() { | |
if (this.mappings == null) { | |
this.mappings = extractMappings(); | |
} | |
return this.mappings; | |
} | |
private List<HandlerMapping> extractMappings() { | |
List<HandlerMapping> list = new ArrayList<>(this.beanFactory.getBeansOfType(HandlerMapping.class).values()); | |
list.remove(this); | |
list.removeIf(handlerMapping -> handlerMapping.getClass().equals(ZuulHandlerMapping.class)); | |
AnnotationAwareOrderComparator.sort(list); | |
return list; | |
} | |
} |
Hi! Yes, tried it, but with the same result. So, not very sure ho is triggering this auto config.
Anyway, maybe it's not a good idea to disable it because there's a lot of necessary stuff there and there's no way to skip the HandlerMapping registration. I've removed this comment.
yeah, excluding Zuul Auto Configuration requires including them back (and some other classes because of visibility) as copies from spring-cloud-netflix
with the only fix for the BasicErrorController
.
But I had to do this with the combination of your fix becasue your gist "as is" does not work for me.
See https://gist.github.com/EugeneGoroschenya/46e1b3bdb8861f110395b1847c03e68c
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi @espartero
Perhaps because there is also
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration
.Did you try with
@SpringBootApplication(exclude = {ZuulServerAutoConfiguration.class, ZuulProxyAutoConfiguration.class})
?