路由是网关的核心功能,既然在spring的框架下,那就要按Spring的规矩来。java
路由规则类:org.springframework.cloud.netflix.zuul.filters.Route 维护这如下信息:spring
private String id; private String fullPath; private String path; private String location; private String prefix; private Boolean retryable; private Set<String> sensitiveHeaders = new LinkedHashSet<>(); private boolean customSensitiveHeaders; private boolean prefixStripped = true;
路由规则维护:RouteLocatorapp
public interface RouteLocator { /** * Ignored route paths (or patterns), if any. */ Collection<String> getIgnoredPaths(); /** * A map of route path (pattern) to location (e.g. service id or URL). */ List<Route> getRoutes(); /** * Maps a path to an actual route with full metadata. */ Route getMatchingRoute(String path); }
类结构以下:
框架
自动配置代码:微服务
@Bean @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class) public DiscoveryClientRouteLocator discoveryRouteLocator() { return new DiscoveryClientRouteLocator(this.server.getServletPrefix(), this.discovery, this.zuulProperties, this.serviceRouteMapper, this.registration); } @Bean @Primary public CompositeRouteLocator primaryRouteLocator( Collection<RouteLocator> routeLocators) { return new CompositeRouteLocator(routeLocators); } @Bean @ConditionalOnMissingBean(SimpleRouteLocator.class) public SimpleRouteLocator simpleRouteLocator() { return new SimpleRouteLocator(this.server.getServletPrefix(), this.zuulProperties); }
这里会使用DiscoveryClientRouteLocator,它作了一个事就是利用DiscoveryClient把注册中心的信息捞过来直接作映射成为路由规则列表。具体代码写的也有差点意思的,好比下图:
this
首先有ZuulController和ZuulHandlerMapping,请求进来先在ZuulHandlerMapping里的列表上找有没有,若是有就把请求丢给ZuulController处理。因此里面必定维护这个一个<path,ZuulController>这么个map。这个匹配规则哪里来呢,通常咱们认为是配置,但这里用了spring cloud,它加了注册的微服务动态加入匹配规则的逻辑,也就是下面的DiscoveryClientRouteLocator。
ZuulHandlerMapping的lookupHandler方法:url
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) { 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 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.dirty来控制是否是从新调用registerHandlers,看代码是执行一遍后,列表被存下来后面进来就用这个列表就好了。这里有一个点,就是每次心跳事件,就是应用和注册中心保持的心跳的时候会把这个从新改为true,从而再执行到locateRoutes方法,就能够从新在内存里拿注册中心可能同步过来的新的信息。.net
注意ZuulProxyAutoConfiguration中ZuulDiscoveryRefreshListener的onApplicationEvent方法:code
public void onApplicationEvent(ApplicationEvent event) { if (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.monitor.update(value)) { reset(); } } private void reset() { this.zuulHandlerMapping.setDirty(true); }
locateRoutes方法就是从注册中心同步过来的全部注册的应用,转成一个个路由规则,若是本身在配置上配置了的路由规则,则按配置的来,没配置的就补上。这里注意了,也就是说无论你配置没配置,只要注册上来的,这个网关的路由规则上就有!问题是,不少微服务提供的接口并不想给网关用,甚至从分层的角度上来讲某些应用属于基础服务应用,只想给上层的业务应用调用,并不想直接由网关暴露出去。那么是否是有问题呢,这个也不算是问题,人家提供的框架原本就是要你在这个基础上改造的。这个后续再讲。
locateRoutes方法:server
protected LinkedHashMap<String, ZuulRoute> locateRoutes() { LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>(); routesMap.putAll(super.locateRoutes()); if (this.discovery != null) { Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>(); for (ZuulRoute route : routesMap.values()) { String serviceId = route.getServiceId(); if (serviceId == null) { serviceId = route.getId(); } if (serviceId != null) { staticServices.put(serviceId, route); } } // Add routes for discovery services by default List<String> services = this.discovery.getServices(); String[] ignored = this.properties.getIgnoredServices() .toArray(new String[0]); for (String serviceId : services) { // Ignore specifically ignored services and those that were manually // configured 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)); } } } 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 (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; }