This article is a long one. The first two parts mainly talk about the principles and source code. Small partners who need to see the implementation directly can jump to the third part through the directory
catalogue
2. interpretation of zuul built-in route interceptor source code
2.1 description of frame interceptor
3.2 realize routing management
1. Zuul request process
Zuul is a gateway component based on Servlet. Zuul defines four types of interceptors: pre interceptor, route interceptor, post interceptor and error interceptor
The routing function of the framework itself is also implemented based on interceptors
When @EnableZuulProxy is enabled, the framework will inject a batch of built-in interceptors into the container when the service is started, including route related interceptors.
When a request comes in, it will enter these interceptors in turn, and the interceptors will forward the request.
The following is the general request flow of a request in the routing function

It can be seen that after a request comes in, it will first be judged in the front interceptor whether it conforms to the existing routing rules. If the request path matches the routing rules, the request will be marked with some relevant marks, and then the request will be forwarded when entering the framework by the built-in route interceptor
This involves a very important point, that is, when the pre interceptor matches the routing rules of the request, it must first obtain the list of routing rules. Therefore, the pre interceptor must load the routing rules from somewhere. Therefore, to realize the dynamic management of routing rules, our main idea is
- Find out where Zuul loads routing rules
- Replicate its load logic so that it can load routes from the data source we defined
So let's take a look at what interceptors Zuul has built in and how the interceptors work
2. interpretation of zuul built-in route interceptor source code
2.1 description of frame interceptor
Zuul mainly relies on the following three interceptors to realize Request Routing:
org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter
The front interceptor determines whether the request matches the existing routing rules. If so, it injects relevant routing information into the request container so that the target routing address can be obtained during the actual routing
org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter
The service routing rules automatically discovered from the registry and the configured service routes are forwarded by the route interceptor.
Example:
zuul: routes: demo1: path: /demo1/** serviceId: demo1 stripPrefix: false
When accessing
http://localhost:8080/demo1/test
The interceptor will go to the registry to find the service named demo1 and forward the request to the service,
org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter
If an external link route is configured, it will be forwarded by the route. The default implementation is to call the httpClient to send an http request to the routing address to obtain a response
Example:
zuul: routes: demo: path: /demo/** url: http://localhost:8081 stripPrefix: false
When accessing:
- http://localhost:8080/demo/test
The interceptor will enter the interceptor, and the interceptor will send the
- http:localhost:8081/demo/test
Send request
2.2 source code analysis
View the source code of PreDecorationFilter
@Override public Object run() { // Get request container (stored in ThreadLocal, unique to threads) RequestContext ctx = RequestContext.getCurrentContext(); // Get request uri final String requestURI = this.urlPathHelper .getPathWithinApplication(ctx.getRequest()); // Key: judge whether the request uri matches the existing route Route route = this.routeLocator.getMatchingRoute(requestURI); // If yes, set some route related information to the request container, such as the route destination address and prefix if (route != null) { String location = route.getLocation(); if (location != null) { ctx.put(REQUEST_URI_KEY, route.getPath()); ctx.put(PROXY_KEY, route.getId()); /***********Omit a bunch of non primary code*************/ } } else { log.warn("No route found for uri: " + requestURI); String forwardURI = getForwardUri(requestURI); ctx.set(FORWARD_TO_KEY, forwardURI); } return null; }
The logic of this interceptor is simple
- Get request uri
- Get existing routing rules
- Determine whether the uri matches the existing routing rules
- If it matches, set the routing related information in the request container
Here comes the key code
Route route = this.routeLocator.getMatchingRoute(requestURI);
Enter the RouteLocator to see what's inside
package org.springframework.cloud.netflix.zuul.filters; import java.util.Collection; import java.util.List; /** * @author Dave Syer */ public interface RouteLocator { /** * Ignored route paths (or patterns), if any. * @return {@link Collection} of ignored paths */ Collection<String> getIgnoredPaths(); /** * A map of route path (pattern) to location (e.g. service id or URL). * @return {@link List} of routes */ List<Route> getRoutes(); /** * Maps a path to an actual route with full metadata. * @param path used to match the {@link Route} * @return matching {@link Route} based on the provided path */ Route getMatchingRoute(String path); }
From the method name, we can see that this list<route> getroutes() is the method we need to load the route list
View the implementation class relationship uml diagram of this interface

The interface RouteLocator has three implementation classes, which are
-
CompositeRouteLocator: By multiple RouteLocator Composed of RouteLocator,Enable@EnableZuulServer The default implementation of, There is no specific internal implementation, and there are multiple internal Holdings RouteLocator Object, essentially a decorator
-
SimpleRouteLocator: Simple route locator, realizing the function of obtaining local route configuration
-
DiscoveryClientRouteLocator: Enable@EnableZuulProxy The default implementation of the SimpleRouteLocator, Realized RefreshableRouteLocator,It also extends the function of getting routing rules from the registry
debug: when the request enters the prefilter:

It can be seen that the route locator in the prefilter is actually CompositeRouteLocator, and the CompositeRouteLocator object holds the RouteLocator array, and the actual call is discoveryclintroutelocator
Return to the method of getting routes
Look at org Springframework Cloud Netflix Zuul Filters Simpleroutelocator\getroutes
@Override public List<Route> getRoutes() { List<Route> values = new ArrayList<>(); // Mainly focus on the getroutemap () method. The following is just to convert ZuulRoute to Route for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) { ZuulRoute route = entry.getValue(); String path = route.getPath(); try { values.add(getRoute(route, path)); } catch (Exception e) { if (log.isWarnEnabled()) { log.warn("Invalid route, routeId: " + route.getId() + ", routeServiceId: " + route.getServiceId() + ", msg: " + e.getMessage()); } if (log.isDebugEnabled()) { log.debug("", e); } } } return values; }
The getroutes () method does not implement the logic of obtaining routes, but obtains routes from getroutes (). Let's look at getRoutes()
protected Map<String, ZuulRoute> getRoutesMap() { if (this.routes.get() == null) { this.routes.set(locateRoutes()); } return this.routes.get(); }
As you can see, locatoroutes () is the final method to obtain routing rules
protected Map<String, ZuulRoute> locateRoutes() { LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>(); for (ZuulRoute route : this.properties.getRoutes().values()) { routesMap.put(route.getPath(), route); } return routesMap; }
The logic here is also very simple, that is, take ZuulRoute from properties and convert it into map<string, ZuulRoute>, and properties are actually Zuul's configuration objects
package org.springframework.cloud.netflix.zuul.filters; @ConfigurationProperties("zuul") public class ZuulProperties { private String prefix = ""; private boolean stripPrefix = true; private Boolean retryable = false; private Map<String, ZuulRoute> routes = new LinkedHashMap<>(); /** * Omit a bunch of similar code */ public static class ZuulRoute { private String id; private String path; private String serviceId; private String url; private boolean stripPrefix = true; private Boolean retryable; private Set<String> sensitiveHeaders = new LinkedHashSet<>(); private boolean customSensitiveHeaders = false; } }
Are you familiar with this? Isn't this the related attribute of zuul that we configured in the yml file?
SimpleRouteLocator obtains the configured routing rules from the configuration file, and
Discoveryclintroutelocator extends this method, adds the function of obtaining registered services from the registry, and implements the RefreshableRouteLocator interface to implement the refresh function.
@Override protected LinkedHashMap<String, ZuulRoute> locateRoutes() { LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>(); routesMap.putAll(super.locateRoutes()); if (this.discovery != null) { Map<String, ZuulRoute> staticServices = new LinkedHashMap<>(); for (ZuulRoute route : routesMap.values()) { String serviceId = route.getServiceId(); if (serviceId == null) { serviceId = route.getId(); } if (serviceId != null) { staticServices.put(serviceId, route); } } // Add route for discovery service List<String> services = this.discovery.getServices(); String[] ignored = this.properties.getIgnoredServices() .toArray(new String[0]); // Convert registry services to ZuulRoute for (String serviceId : services) { String key = "/" + mapRouteToService(serviceId) + "/**"; if (staticServices.containsKey(serviceId) && staticServices.get(serviceId).getUrl() == null) { // Explicitly configured with no URL, cannot be ignored // all static routes are already in routesMap // Update location using serviceId if location is null ZuulRoute staticRoute = staticServices.get(serviceId); if (!StringUtils.hasText(staticRoute.getLocation())) { staticRoute.setLocation(serviceId); } } if (!PatternMatchUtils.simpleMatch(ignored, serviceId) && !routesMap.containsKey(key)) { // Not ignored routesMap.put(key, new ZuulRoute(key, serviceId)); } } } // Move the default routing rule (/ * *) to the end of the map if (routesMap.get(DEFAULT_ROUTE) != null) { ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE); routesMap.remove(DEFAULT_ROUTE); routesMap.put(DEFAULT_ROUTE, defaultRoute); } // Do some conversion LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>(); for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) { String path = entry.getKey(); // Prepend with slash if not already present. if (!path.startsWith("/")) { path = "/" + path; } if (StringUtils.hasText(this.properties.getPrefix())) { path = this.properties.getPrefix() + path; if (!path.startsWith("/")) { path = "/" + path; } } values.put(path, entry.getValue()); } return values; }
3. implementation
3.1 customize RouteLocator
What we need to do is to inherit discoveryclintroutelocator and rewrite the locateRoutes() method, imitating the way discoveryclintroutelocator extends the route.
/** * Custom route locator * * @author qiudao * @date 2020/7/7 */ @Slf4j public class CustomRouteLocator extends DiscoveryClientRouteLocator { private ZuulProperties properties; private ZuulRouteService zuulRouteService; public CustomRouteLocator(String servletPath, DiscoveryClient discovery, ZuulProperties properties, ServiceInstance localServiceInstance, ZuulRouteService zuulRouteService) { super(servletPath, discovery, properties, localServiceInstance); this.properties = properties; this.zuulRouteService = zuulRouteService; } @Override protected LinkedHashMap<String, ZuulRoute> locateRoutes() { log.info("Custom load route start........................"); LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>(super.locateRoutes()); //----------Only this part is different from the parent class, and all others are the same----------------// // Get route from database List<ZuulRouteEntity> zuulRouteEntities = zuulRouteService.getActiveRoutes(); // conversion LinkedHashMap<String, ZuulRoute> zuulRouteEntitiesMap = this.convert(zuulRouteEntities); routesMap.putAll(zuulRouteEntitiesMap); log.info("Get from database{}Routing rules...........", zuulRouteEntitiesMap.size()); //--------------------------// if (routesMap.get(DEFAULT_ROUTE) != null) { ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE); // Move the defaultServiceId to the end routesMap.remove(DEFAULT_ROUTE); routesMap.put(DEFAULT_ROUTE, defaultRoute); } LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>(); for (Map.Entry<String, ZuulRoute> entry : routesMap.entrySet()) { String path = entry.getKey(); // Prepend with slash if not already present. if (!path.startsWith("/")) { path = "/" + path; } if (StringUtils.hasText(this.properties.getPrefix())) { path = this.properties.getPrefix() + path; if (!path.startsWith("/")) { path = "/" + path; } } values.put(path, entry.getValue()); } log.info("End of custom load route........................"); return values; } /** * Database entities are converted to entities required by Zuul * * @param zuulRouteEntities * @return */ private LinkedHashMap<String, ZuulRoute> convert(List<ZuulRouteEntity> zuulRouteEntities) { LinkedHashMap<String, ZuulRoute> result = new LinkedHashMap<>(); if (zuulRouteEntities == null || zuulRouteEntities.isEmpty()) { return result; } for (ZuulRouteEntity zuulRouteEntity : zuulRouteEntities) { ZuulRoute route = new ZuulRoute(); BeanUtils.copyProperties(zuulRouteEntity, route); String id = StringUtils.isEmpty(route.getServiceId()) ? extractId(route.getPath()) : route.getServiceId(); route.setId(id); String regex = ","; Set<String> sensitiveHeaders = Arrays.stream(zuulRouteEntity.getSensitiveHeaders().split(regex)).collect(Collectors.toSet()); route.setSensitiveHeaders(sensitiveHeaders); result.put("/" + id + "/**", route); if (log.isDebugEnabled()) { log.info("Load route:{}", route.toString()); } } return result; } private String extractId(String path) { path = path.startsWith("/") ? path.substring(1) : path; path = path.replace("/*", "").replace("*", ""); return path; } }
The main task is to rewrite the locatoroutes method. The parent class gets the service list from the registry and converts it into a route. Here, we get the data from the database and convert it into a route.
3.2 realize routing management
ZuulRouteEntity is our customized database entity, and its properties refer to zuulproperties Zuulroute.
/** * @author qiudao * @date 2020/7/5 */ @Data @Accessors(chain = true) @TableName("route") @ToString public class ZuulRouteEntity { @TableId(value = "id", type = IdType.ASSIGN_ID) private String id; private String path; private String serviceId; private String url; private Boolean stripPrefix = true; private Boolean retryable = false; private String sensitiveHeaders = ""; private Boolean customSensitiveHeaders = false; private Boolean enable = true; }
Then add the CRUD method of the entity, and reload the route after the operation is successful

Because our customized route loader inherits from discoveryclintroutelocator, and it implements the RefreshableRouteLocator refresh interface, if we need to refresh the route, we just need to get this Bean through Spring and call its refresh() method
3.3 effect display
A new test application with port 8082 (Zuul application port 8080)

At this time, the routing rule has not been configured. First, try to access the interface of 8082 through zuul (the routing rule has not been configured at this time)
Then add a rule through the interface
You can see that the console has printed the relevant logs for obtaining routes. Now let's access this interface again
It can be seen that the routing rules have been adjusted normally. So far, the function of dynamic management of routing rules has been realized.
4. summary
There are a lot of BB. In fact, there are only a few lines of key code