Zuul路由源码
Zuul对于动态路由的实现,默认是从配置文件里面去拿到相关的配置数据的。
比如在 application.yml 里面做如下配置
zuul:routes:service1:path: /api/service1/**serviceId: service1github:path: /github/**url: https://github.com/
ZuulProperties
@ConfigurationProperties("zuul")public class ZuulProperties {public static final List<String> SECURITY_HEADERS = Arrays.asList("Pragma", "Cache-Control", "X-Frame-Options", "X-Content-Type-Options", "X-XSS-Protection", "Expires");private String prefix = "";private boolean stripPrefix = true;private Boolean retryable = false;private Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap();private boolean addProxyHeaders = true;private boolean addHostHeader = false;private Set<String> ignoredServices = new LinkedHashSet();private Set<String> ignoredPatterns = new LinkedHashSet();private Set<String> ignoredHeaders = new LinkedHashSet();private boolean ignoreSecurityHeaders = true;private boolean forceOriginalQueryStringEncoding = false;private String servletPath = "/zuul";private boolean ignoreLocalService = true;private ZuulProperties.Host host = new ZuulProperties.Host();private boolean traceRequestBody = true;private boolean removeSemicolonContent = true;private Set<String> sensitiveHeaders = new LinkedHashSet(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));private boolean sslHostnameValidationEnabled = true;private ExecutionIsolationStrategy ribbonIsolationStrategy;private ZuulProperties.HystrixSemaphore semaphore;private ZuulProperties.HystrixThreadPool threadPool;............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;............}}
路由信息配置前置过滤器
public class PreDecorationFilter extends ZuulFilter {public PreDecorationFilter(RouteLocator routeLocator, String dispatcherServletPath, ZuulProperties properties, ProxyRequestHelper proxyRequestHelper) {this.routeLocator = routeLocator;this.properties = properties;this.urlPathHelper.setRemoveSemicolonContent(properties.isRemoveSemicolonContent());this.dispatcherServletPath = dispatcherServletPath;this.proxyRequestHelper = proxyRequestHelper;}@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();// 根据request提取requestURIString requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());Route route = this.routeLocator.getMatchingRoute(requestURI);String location;String xforwardedfor;String remoteAddr;if (route != null) {location = route.getLocation();if (location != null) {ctx.put("requestURI", route.getPath());ctx.put("proxy", route.getId());if (!route.isCustomSensitiveHeaders()) {this.proxyRequestHelper.addIgnoredHeaders((String[])this.properties.getSensitiveHeaders().toArray(new String[0]));} else {this.proxyRequestHelper.addIgnoredHeaders((String[])route.getSensitiveHeaders().toArray(new String[0]));}if (route.getRetryable() != null) {ctx.put("retryable", route.getRetryable());}if (!location.startsWith("http:") && !location.startsWith("https:")) {if (location.startsWith("forward:")) {ctx.set("forward.to", StringUtils.cleanPath(location.substring("forward:".length()) + route.getPath()));ctx.setRouteHost((URL)null);return null;}// 排除 http: 、 https: 、 forward:ctx.set("serviceId", location);ctx.setRouteHost((URL)null);ctx.addOriginResponseHeader("X-Zuul-ServiceId", location);} else { // http: 或 https:ctx.setRouteHost(this.getUrl(location));ctx.addOriginResponseHeader("X-Zuul-Service", location);}if (this.properties.isAddProxyHeaders()) {this.addProxyHeaders(ctx, route);xforwardedfor = ctx.getRequest().getHeader("X-Forwarded-For");remoteAddr = ctx.getRequest().getRemoteAddr();if (xforwardedfor == null) {xforwardedfor = remoteAddr;} else if (!xforwardedfor.contains(remoteAddr)) {xforwardedfor = xforwardedfor + ", " + remoteAddr;}ctx.addZuulRequestHeader("X-Forwarded-For", xforwardedfor);}if (this.properties.isAddHostHeader()) {ctx.addZuulRequestHeader("Host", this.toHostHeader(ctx.getRequest()));}}} else {log.warn("No route found for uri: " + requestURI);xforwardedfor = this.dispatcherServletPath;if (RequestUtils.isZuulServletRequest()) {log.debug("zuulServletPath=" + this.properties.getServletPath());location = requestURI.replaceFirst(this.properties.getServletPath(), "");log.debug("Replaced Zuul servlet path:" + location);} else {log.debug("dispatcherServletPath=" + this.dispatcherServletPath);location = requestURI.replaceFirst(this.dispatcherServletPath, "");log.debug("Replaced DispatcherServlet servlet path:" + location);}if (!location.startsWith("/")) {location = "/" + location;}remoteAddr = xforwardedfor + location;remoteAddr = remoteAddr.replaceAll("//", "/");ctx.set("forward.to", remoteAddr);}return null;}}############# 分割线 ##################this.routeLocator.getMatchingRoute(requestURI) 是重点,根据请求的URL获取Route,再根据Route的location是否http:、https:、forward: 前缀来设置属性。例如访问http://location:8080/service1/echo-> Route(id='service1', fullPath='/service1/echo', path='/echo', location='service1', prefix='/service1')http://location:8080/github/echo-> Route(id='github', fullPath='/github/echo', path='/echo', location='https://github.com/', prefix='/github')
路由定位
PreDecorationFilter 中通过 RouteLocator 根据URL获取Route。
动态路由可以通过扩展 RouteLocator 来完成。
public interface RouteLocator {Collection<String> getIgnoredPaths();List<Route> getRoutes();Route getMatchingRoute(String path);}### RouteLocator主要能力有: 根据path获取Route 和 获取所有的Route
RefreshableRouteLocator
public interface RefreshableRouteLocator extends RouteLocator {void refresh();}
SimpleRouteLocator
简单的路由定位器,路由信息来自 ZuulProperties 属性配置类, locateRoutes() 是定位路由的核心,从 ZuulProperties 中加载路由数据。
protected Map<String, ZuulRoute> locateRoutes() {// routesMap 存储路由信息LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap();Iterator var2 = this.properties.getRoutes().values().iterator();// 查找路由信息while(var2.hasNext()) {ZuulRoute route = (ZuulRoute)var2.next();routesMap.put(route.getPath(), route);}return routesMap;}
DiscoveryClientRouteLocator
基于DiscoveryClient,路由数据来自 properties 中的静态配置和 DiscoveryClient 从注册中心获取的数据。DiscoveryClientRouteLocator 拥有几个重要能力: 动态添加路由,刷新路由,获取路由信息(用途不大)
public class DiscoveryClientRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {public DiscoveryClientRouteLocator(String servletPath, DiscoveryClient discovery, ZuulProperties properties, ServiceInstance localServiceInstance) {super(servletPath, properties);if (properties.isIgnoreLocalService() && localServiceInstance != null) {String localServiceId = localServiceInstance.getServiceId();if (!properties.getIgnoredServices().contains(localServiceId)) {properties.getIgnoredServices().add(localServiceId);}}this.serviceRouteMapper = new SimpleServiceRouteMapper();this.discovery = discovery;this.properties = properties;}public void addRoute(String path, String location) {this.properties.getRoutes().put(path, new ZuulRoute(path, location));this.refresh();}public void addRoute(ZuulRoute route) {this.properties.getRoutes().put(route.getPath(), route);this.refresh();}protected LinkedHashMap<String, ZuulRoute> locateRoutes() {LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap();// 通过父类 SimpleRouteLocator获取静态路由信息routesMap.putAll(super.locateRoutes());LinkedHashMap values;Iterator var3;String path;if (this.discovery != null) {values = new LinkedHashMap();var3 = routesMap.values().iterator();while(var3.hasNext()) {ZuulRoute route = (ZuulRoute)var3.next();path = route.getServiceId();if (path == null) {path = route.getId();}if (path != null) {values.put(path, route);}}// 通过DiscoveryClient获取路由信息List<String> services = this.discovery.getServices();String[] ignored = (String[])this.properties.getIgnoredServices().toArray(new String[0]);Iterator var13 = services.iterator();while(var13.hasNext()) {String serviceId = (String)var13.next();String key = "/" + this.mapRouteToService(serviceId) + "/**";if (values.containsKey(serviceId) && ((ZuulRoute)values.get(serviceId)).getUrl() == null) {ZuulRoute staticRoute = (ZuulRoute)values.get(serviceId);if (!StringUtils.hasText(staticRoute.getLocation())) {staticRoute.setLocation(serviceId);}}if (!PatternMatchUtils.simpleMatch(ignored, serviceId) && !routesMap.containsKey(key)) {routesMap.put(key, new ZuulRoute(key, serviceId));}}}if (routesMap.get("/**") != null) {ZuulRoute defaultRoute = (ZuulRoute)routesMap.get("/**");routesMap.remove("/**");routesMap.put("/**", defaultRoute);}values = new LinkedHashMap();Entry entry;for(var3 = routesMap.entrySet().iterator(); var3.hasNext(); values.put(path, entry.getValue())) {entry = (Entry)var3.next();path = (String)entry.getKey();if (!path.startsWith("/")) {path = "/" + path;}if (StringUtils.hasText(this.properties.getPrefix())) {path = this.properties.getPrefix() + path;if (!path.startsWith("/")) {path = "/" + path;}}}return values;}// 刷新时会调用 locateRoute()public void refresh() {this.doRefresh();}}以service1为例,配置 /api/service1/** -> service1, 存储的路由信息为:/api/service1/** --> service1 # 根据静态路由配置生成的路由规则/service1/** --> service1 # 利用DiscoveryClient提取后根据默认规则生成的路由信息(用处不大)
CompositeRouteLocator
具备组合多个 RouteLocator 的能力,用 Collection 存储多个 RouteLocator ,调用 getRoutes() 、 getMatchingRoute() 、 refresh() 都会逐个调用每一个 RouteLocator 相应的方法。
public class CompositeRouteLocator implements RefreshableRouteLocator {private final Collection<? extends RouteLocator> routeLocators;private ArrayList<RouteLocator> rl;public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {Assert.notNull(routeLocators, "'routeLocators' must not be null");this.rl = new ArrayList(routeLocators);AnnotationAwareOrderComparator.sort(this.rl);this.routeLocators = this.rl;}public List<Route> getRoutes() {List<Route> route = new ArrayList();Iterator var2 = this.routeLocators.iterator();while(var2.hasNext()) {RouteLocator locator = (RouteLocator)var2.next();route.addAll(locator.getRoutes());}return route;}public Route getMatchingRoute(String path) {Iterator var2 = this.routeLocators.iterator();Route route;do {if (!var2.hasNext()) {return null;}RouteLocator locator = (RouteLocator)var2.next();route = locator.getMatchingRoute(path);} while(route == null);return route;}public void refresh() {Iterator var1 = this.routeLocators.iterator();while(var1.hasNext()) {RouteLocator locator = (RouteLocator)var1.next();if (locator instanceof RefreshableRouteLocator) {((RefreshableRouteLocator)locator).refresh();}}}}
Zuul动态路由实现
数据表实现
id | path | service_id | url | retryable | enable | strip_prefix | api_name主键 路径 服务名称 url 可重试 是否可用 带前缀 接口名称CREATE TABLE `gateway_api_route` (`id` varchar(50) NOT NULL,`path` varchar(255) NOT NULL,`service_id` varchar(50) DEFAULT NULL,`url` varchar(255) DEFAULT NULL,`retryable` tinyint(1) DEFAULT NULL,`enabled` tinyint(1) NOT NULL,`strip_prefix` int(11) DEFAULT NULL,`api_name` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8INSERT INTO gateway_api_route (id, path, service_id, retryable, strip_prefix, url, enabled)VALUES ('order-service', '/order/**', 'order-service',0,1, NULL, 1);
异步路由定位器
import org.springframework.beans.BeanUtils;import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.util.StringUtils;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;public class DynamicRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {private JdbcTemplate jdbcTemplate;private ZuulProperties properties;public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}public DynamicRouteLocator(String servletPath, ZuulProperties properties) {super(servletPath, properties);this.properties = properties;}@Overridepublic void refresh() {doRefresh();}@Overrideprotected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<>();// 加载application.yml中的路由表routesMap.putAll(super.locateRoutes());// 加载db中的路由表routesMap.putAll(locateRoutesFromDB());// 统一处理一下路由path的格式LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : routesMap.entrySet()) {String path = entry.getKey();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());}System.out.println("路由表:" + values);return values;}private Map<String, ZuulProperties.ZuulRoute> locateRoutesFromDB() {// routesMap 存储路由信息Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap<>();// 从数据表里获取启用的路由服务List<GatewayApiRoute> results = jdbcTemplate.query("select * from gateway_api_route where enabled = true ",new BeanPropertyRowMapper<>(GatewayApiRoute.class));for (GatewayApiRoute result : results) {if (StringUtils.isEmpty(result.getPath()) ) {continue;}if (StringUtils.isEmpty(result.getServiceId()) && StringUtils.isEmpty(result.getUrl())) {continue;}ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();try {BeanUtils.copyProperties(result, zuulRoute);} catch (Exception e) {e.printStackTrace();}routes.put(zuulRoute.getPath(), zuulRoute);}return routes;}}
public class GatewayApiRoute {private String id;private String path;private String serviceId;private String url;private boolean stripPrefix = true;private Boolean retryable;private Boolean enabled;}
@Configurationpublic class DynamicRouteConfiguration {@Autowiredprivate ZuulProperties zuulProperties;@Autowiredprivate ServerProperties server;@Autowiredprivate JdbcTemplate jdbcTemplate;@Beanpublic DynamicRouteLocator routeLocator() {DynamicRouteLocator routeLocator = new DynamicRouteLocator(this.server.getServletPrefix(), this.zuulProperties);routeLocator.setJdbcTemplate(jdbcTemplate);return routeLocator;}}
定时刷新路由信息
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;import org.springframework.cloud.netflix.zuul.filters.RouteLocator;import org.springframework.context.ApplicationEventPublisher;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;@Component@Configuration@EnableSchedulingpublic class RefreshRouteTask {@Autowiredprivate ApplicationEventPublisher publisher;@Autowiredprivate RouteLocator routeLocator;@Scheduled(fixedRate = 5000)private void refreshRoute() {System.out.println("定时刷新路由表");RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);publisher.publishEvent(routesRefreshedEvent);}}
