-
-
Save espartero/d38e64847d584775f708086b85cef2d2 to your computer and use it in GitHub Desktop.
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); | |
} | |
} | |
} | |
} |
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); | |
} | |
} | |
} |
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 @espartero
... I was not able to disable {@link org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration}.
Perhaps because there is also public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration
.
Did you try with @SpringBootApplication(exclude = {ZuulServerAutoConfiguration.class, ZuulProxyAutoConfiguration.class})
?
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
Setting up a project with Spring Boot 2.5.2 and Netflix Zuul fails with NoSuchMethodError: ErrorController.getErrorPath().
This is commented here and suggested a fix here, but there's no plan to integrate it.
The idea is to remove the default ZuulHandlerMapping from the DispatchServlet (done in ZuulExcludedCompositeHandlerMapping), and register a new one (Spring5ZuulHandlerMapping) that it's just a copy of ZuulHandlerMapping but fixing the compilation error. Note that one application listener is also registered, implementing the same behavior that the old one. Not sure how to call dispatcherServlet.setDetectAllHandlerMappings(false), so ended up calling it on a configuration class, just for test purposes.
Not fully tested!!