Zuul路由源码

Zuul对于动态路由的实现,默认是从配置文件里面去拿到相关的配置数据的。
比如在 application.yml 里面做如下配置

  1. zuul:
  2. routes:
  3. service1:
  4. path: /api/service1/**
  5. serviceId: service1
  6. github:
  7. path: /github/**
  8. url: https://github.com/

ZuulProperties

  1. @ConfigurationProperties("zuul")
  2. public class ZuulProperties {
  3. public static final List<String> SECURITY_HEADERS = Arrays.asList("Pragma", "Cache-Control", "X-Frame-Options", "X-Content-Type-Options", "X-XSS-Protection", "Expires");
  4. private String prefix = "";
  5. private boolean stripPrefix = true;
  6. private Boolean retryable = false;
  7. private Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap();
  8. private boolean addProxyHeaders = true;
  9. private boolean addHostHeader = false;
  10. private Set<String> ignoredServices = new LinkedHashSet();
  11. private Set<String> ignoredPatterns = new LinkedHashSet();
  12. private Set<String> ignoredHeaders = new LinkedHashSet();
  13. private boolean ignoreSecurityHeaders = true;
  14. private boolean forceOriginalQueryStringEncoding = false;
  15. private String servletPath = "/zuul";
  16. private boolean ignoreLocalService = true;
  17. private ZuulProperties.Host host = new ZuulProperties.Host();
  18. private boolean traceRequestBody = true;
  19. private boolean removeSemicolonContent = true;
  20. private Set<String> sensitiveHeaders = new LinkedHashSet(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
  21. private boolean sslHostnameValidationEnabled = true;
  22. private ExecutionIsolationStrategy ribbonIsolationStrategy;
  23. private ZuulProperties.HystrixSemaphore semaphore;
  24. private ZuulProperties.HystrixThreadPool threadPool;
  25. ......
  26. ......
  27. public static class ZuulRoute {
  28. private String id;
  29. private String path;
  30. private String serviceId;
  31. private String url;
  32. private boolean stripPrefix = true;
  33. private Boolean retryable;
  34. private Set<String> sensitiveHeaders = new LinkedHashSet();
  35. private boolean customSensitiveHeaders = false;
  36. ......
  37. ......
  38. }
  39. }

路由信息配置前置过滤器

  1. public class PreDecorationFilter extends ZuulFilter {
  2. public PreDecorationFilter(RouteLocator routeLocator, String dispatcherServletPath, ZuulProperties properties, ProxyRequestHelper proxyRequestHelper) {
  3. this.routeLocator = routeLocator;
  4. this.properties = properties;
  5. this.urlPathHelper.setRemoveSemicolonContent(properties.isRemoveSemicolonContent());
  6. this.dispatcherServletPath = dispatcherServletPath;
  7. this.proxyRequestHelper = proxyRequestHelper;
  8. }
  9. @Override
  10. public Object run() {
  11. RequestContext ctx = RequestContext.getCurrentContext();
  12. // 根据request提取requestURI
  13. String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
  14. Route route = this.routeLocator.getMatchingRoute(requestURI);
  15. String location;
  16. String xforwardedfor;
  17. String remoteAddr;
  18. if (route != null) {
  19. location = route.getLocation();
  20. if (location != null) {
  21. ctx.put("requestURI", route.getPath());
  22. ctx.put("proxy", route.getId());
  23. if (!route.isCustomSensitiveHeaders()) {
  24. this.proxyRequestHelper.addIgnoredHeaders((String[])this.properties.getSensitiveHeaders().toArray(new String[0]));
  25. } else {
  26. this.proxyRequestHelper.addIgnoredHeaders((String[])route.getSensitiveHeaders().toArray(new String[0]));
  27. }
  28. if (route.getRetryable() != null) {
  29. ctx.put("retryable", route.getRetryable());
  30. }
  31. if (!location.startsWith("http:") && !location.startsWith("https:")) {
  32. if (location.startsWith("forward:")) {
  33. ctx.set("forward.to", StringUtils.cleanPath(location.substring("forward:".length()) + route.getPath()));
  34. ctx.setRouteHost((URL)null);
  35. return null;
  36. }
  37. // 排除 http: 、 https: 、 forward:
  38. ctx.set("serviceId", location);
  39. ctx.setRouteHost((URL)null);
  40. ctx.addOriginResponseHeader("X-Zuul-ServiceId", location);
  41. } else { // http: 或 https:
  42. ctx.setRouteHost(this.getUrl(location));
  43. ctx.addOriginResponseHeader("X-Zuul-Service", location);
  44. }
  45. if (this.properties.isAddProxyHeaders()) {
  46. this.addProxyHeaders(ctx, route);
  47. xforwardedfor = ctx.getRequest().getHeader("X-Forwarded-For");
  48. remoteAddr = ctx.getRequest().getRemoteAddr();
  49. if (xforwardedfor == null) {
  50. xforwardedfor = remoteAddr;
  51. } else if (!xforwardedfor.contains(remoteAddr)) {
  52. xforwardedfor = xforwardedfor + ", " + remoteAddr;
  53. }
  54. ctx.addZuulRequestHeader("X-Forwarded-For", xforwardedfor);
  55. }
  56. if (this.properties.isAddHostHeader()) {
  57. ctx.addZuulRequestHeader("Host", this.toHostHeader(ctx.getRequest()));
  58. }
  59. }
  60. } else {
  61. log.warn("No route found for uri: " + requestURI);
  62. xforwardedfor = this.dispatcherServletPath;
  63. if (RequestUtils.isZuulServletRequest()) {
  64. log.debug("zuulServletPath=" + this.properties.getServletPath());
  65. location = requestURI.replaceFirst(this.properties.getServletPath(), "");
  66. log.debug("Replaced Zuul servlet path:" + location);
  67. } else {
  68. log.debug("dispatcherServletPath=" + this.dispatcherServletPath);
  69. location = requestURI.replaceFirst(this.dispatcherServletPath, "");
  70. log.debug("Replaced DispatcherServlet servlet path:" + location);
  71. }
  72. if (!location.startsWith("/")) {
  73. location = "/" + location;
  74. }
  75. remoteAddr = xforwardedfor + location;
  76. remoteAddr = remoteAddr.replaceAll("//", "/");
  77. ctx.set("forward.to", remoteAddr);
  78. }
  79. return null;
  80. }
  81. }
  82. ############# 分割线 ##################
  83. this.routeLocator.getMatchingRoute(requestURI) 是重点,根据请求的URL获取Route,再根据Routelocation
  84. 是否http:、https:、forward: 前缀来设置属性。
  85. 例如访问
  86. http://location:8080/service1/echo
  87. -> Route(id='service1', fullPath='/service1/echo', path='/echo', location='service1', prefix='/service1')
  88. http://location:8080/github/echo
  89. -> Route(id='github', fullPath='/github/echo', path='/echo', location='https://github.com/', prefix='/github')

路由定位

PreDecorationFilter 中通过 RouteLocator 根据URL获取Route。
动态路由可以通过扩展 RouteLocator 来完成。

  1. public interface RouteLocator {
  2. Collection<String> getIgnoredPaths();
  3. List<Route> getRoutes();
  4. Route getMatchingRoute(String path);
  5. }
  6. ### RouteLocator主要能力有: 根据path获取Route 获取所有的Route

相关类图
image.png

RefreshableRouteLocator

  1. public interface RefreshableRouteLocator extends RouteLocator {
  2. void refresh();
  3. }

SimpleRouteLocator

简单的路由定位器,路由信息来自 ZuulProperties 属性配置类, locateRoutes() 是定位路由的核心,从 ZuulProperties 中加载路由数据。

  1. protected Map<String, ZuulRoute> locateRoutes() {
  2. // routesMap 存储路由信息
  3. LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap();
  4. Iterator var2 = this.properties.getRoutes().values().iterator();
  5. // 查找路由信息
  6. while(var2.hasNext()) {
  7. ZuulRoute route = (ZuulRoute)var2.next();
  8. routesMap.put(route.getPath(), route);
  9. }
  10. return routesMap;
  11. }

DiscoveryClientRouteLocator

基于DiscoveryClient,路由数据来自 properties 中的静态配置和 DiscoveryClient 从注册中心获取的数据。
DiscoveryClientRouteLocator 拥有几个重要能力: 动态添加路由,刷新路由,获取路由信息(用途不大)

  1. public class DiscoveryClientRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
  2. public DiscoveryClientRouteLocator(String servletPath, DiscoveryClient discovery, ZuulProperties properties, ServiceInstance localServiceInstance) {
  3. super(servletPath, properties);
  4. if (properties.isIgnoreLocalService() && localServiceInstance != null) {
  5. String localServiceId = localServiceInstance.getServiceId();
  6. if (!properties.getIgnoredServices().contains(localServiceId)) {
  7. properties.getIgnoredServices().add(localServiceId);
  8. }
  9. }
  10. this.serviceRouteMapper = new SimpleServiceRouteMapper();
  11. this.discovery = discovery;
  12. this.properties = properties;
  13. }
  14. public void addRoute(String path, String location) {
  15. this.properties.getRoutes().put(path, new ZuulRoute(path, location));
  16. this.refresh();
  17. }
  18. public void addRoute(ZuulRoute route) {
  19. this.properties.getRoutes().put(route.getPath(), route);
  20. this.refresh();
  21. }
  22. protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
  23. LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap();
  24. // 通过父类 SimpleRouteLocator获取静态路由信息
  25. routesMap.putAll(super.locateRoutes());
  26. LinkedHashMap values;
  27. Iterator var3;
  28. String path;
  29. if (this.discovery != null) {
  30. values = new LinkedHashMap();
  31. var3 = routesMap.values().iterator();
  32. while(var3.hasNext()) {
  33. ZuulRoute route = (ZuulRoute)var3.next();
  34. path = route.getServiceId();
  35. if (path == null) {
  36. path = route.getId();
  37. }
  38. if (path != null) {
  39. values.put(path, route);
  40. }
  41. }
  42. // 通过DiscoveryClient获取路由信息
  43. List<String> services = this.discovery.getServices();
  44. String[] ignored = (String[])this.properties.getIgnoredServices().toArray(new String[0]);
  45. Iterator var13 = services.iterator();
  46. while(var13.hasNext()) {
  47. String serviceId = (String)var13.next();
  48. String key = "/" + this.mapRouteToService(serviceId) + "/**";
  49. if (values.containsKey(serviceId) && ((ZuulRoute)values.get(serviceId)).getUrl() == null) {
  50. ZuulRoute staticRoute = (ZuulRoute)values.get(serviceId);
  51. if (!StringUtils.hasText(staticRoute.getLocation())) {
  52. staticRoute.setLocation(serviceId);
  53. }
  54. }
  55. if (!PatternMatchUtils.simpleMatch(ignored, serviceId) && !routesMap.containsKey(key)) {
  56. routesMap.put(key, new ZuulRoute(key, serviceId));
  57. }
  58. }
  59. }
  60. if (routesMap.get("/**") != null) {
  61. ZuulRoute defaultRoute = (ZuulRoute)routesMap.get("/**");
  62. routesMap.remove("/**");
  63. routesMap.put("/**", defaultRoute);
  64. }
  65. values = new LinkedHashMap();
  66. Entry entry;
  67. for(var3 = routesMap.entrySet().iterator(); var3.hasNext(); values.put(path, entry.getValue())) {
  68. entry = (Entry)var3.next();
  69. path = (String)entry.getKey();
  70. if (!path.startsWith("/")) {
  71. path = "/" + path;
  72. }
  73. if (StringUtils.hasText(this.properties.getPrefix())) {
  74. path = this.properties.getPrefix() + path;
  75. if (!path.startsWith("/")) {
  76. path = "/" + path;
  77. }
  78. }
  79. }
  80. return values;
  81. }
  82. // 刷新时会调用 locateRoute()
  83. public void refresh() {
  84. this.doRefresh();
  85. }
  86. }
  87. service1为例,配置 /api/service1/** -> service1, 存储的路由信息为:
  88. /api/service1/** --> service1 # 根据静态路由配置生成的路由规则
  89. /service1/** --> service1 # 利用DiscoveryClient提取后根据默认规则生成的路由信息(用处不大)

CompositeRouteLocator

具备组合多个 RouteLocator 的能力,用 Collection 存储多个 RouteLocator ,调用 getRoutes()getMatchingRoute()refresh() 都会逐个调用每一个 RouteLocator 相应的方法。

  1. public class CompositeRouteLocator implements RefreshableRouteLocator {
  2. private final Collection<? extends RouteLocator> routeLocators;
  3. private ArrayList<RouteLocator> rl;
  4. public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {
  5. Assert.notNull(routeLocators, "'routeLocators' must not be null");
  6. this.rl = new ArrayList(routeLocators);
  7. AnnotationAwareOrderComparator.sort(this.rl);
  8. this.routeLocators = this.rl;
  9. }
  10. public List<Route> getRoutes() {
  11. List<Route> route = new ArrayList();
  12. Iterator var2 = this.routeLocators.iterator();
  13. while(var2.hasNext()) {
  14. RouteLocator locator = (RouteLocator)var2.next();
  15. route.addAll(locator.getRoutes());
  16. }
  17. return route;
  18. }
  19. public Route getMatchingRoute(String path) {
  20. Iterator var2 = this.routeLocators.iterator();
  21. Route route;
  22. do {
  23. if (!var2.hasNext()) {
  24. return null;
  25. }
  26. RouteLocator locator = (RouteLocator)var2.next();
  27. route = locator.getMatchingRoute(path);
  28. } while(route == null);
  29. return route;
  30. }
  31. public void refresh() {
  32. Iterator var1 = this.routeLocators.iterator();
  33. while(var1.hasNext()) {
  34. RouteLocator locator = (RouteLocator)var1.next();
  35. if (locator instanceof RefreshableRouteLocator) {
  36. ((RefreshableRouteLocator)locator).refresh();
  37. }
  38. }
  39. }
  40. }

Zuul动态路由实现

数据表实现

  1. id | path | service_id | url | retryable | enable | strip_prefix | api_name
  2. 主键 路径 服务名称 url 可重试 是否可用 带前缀 接口名称
  3. CREATE TABLE `gateway_api_route` (
  4. `id` varchar(50) NOT NULL,
  5. `path` varchar(255) NOT NULL,
  6. `service_id` varchar(50) DEFAULT NULL,
  7. `url` varchar(255) DEFAULT NULL,
  8. `retryable` tinyint(1) DEFAULT NULL,
  9. `enabled` tinyint(1) NOT NULL,
  10. `strip_prefix` int(11) DEFAULT NULL,
  11. `api_name` varchar(255) DEFAULT NULL,
  12. PRIMARY KEY (`id`)
  13. ) ENGINE=InnoDB DEFAULT CHARSET=utf8
  14. INSERT INTO gateway_api_route (id, path, service_id, retryable, strip_prefix, url, enabled)
  15. VALUES ('order-service', '/order/**', 'order-service',0,1, NULL, 1);

异步路由定位器

  1. import org.springframework.beans.BeanUtils;
  2. import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;
  3. import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
  4. import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
  5. import org.springframework.jdbc.core.BeanPropertyRowMapper;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import org.springframework.util.StringUtils;
  8. import java.util.LinkedHashMap;
  9. import java.util.List;
  10. import java.util.Map;
  11. public class DynamicRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
  12. private JdbcTemplate jdbcTemplate;
  13. private ZuulProperties properties;
  14. public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
  15. this.jdbcTemplate = jdbcTemplate;
  16. }
  17. public DynamicRouteLocator(String servletPath, ZuulProperties properties) {
  18. super(servletPath, properties);
  19. this.properties = properties;
  20. }
  21. @Override
  22. public void refresh() {
  23. doRefresh();
  24. }
  25. @Override
  26. protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
  27. LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<>();
  28. // 加载application.yml中的路由表
  29. routesMap.putAll(super.locateRoutes());
  30. // 加载db中的路由表
  31. routesMap.putAll(locateRoutesFromDB());
  32. // 统一处理一下路由path的格式
  33. LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();
  34. for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : routesMap.entrySet()) {
  35. String path = entry.getKey();
  36. if (!path.startsWith("/")) {
  37. path = "/" + path;
  38. }
  39. if (StringUtils.hasText(this.properties.getPrefix())) {
  40. path = this.properties.getPrefix() + path;
  41. if (!path.startsWith("/")) {
  42. path = "/" + path;
  43. }
  44. }
  45. values.put(path, entry.getValue());
  46. }
  47. System.out.println("路由表:" + values);
  48. return values;
  49. }
  50. private Map<String, ZuulProperties.ZuulRoute> locateRoutesFromDB() {
  51. // routesMap 存储路由信息
  52. Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap<>();
  53. // 从数据表里获取启用的路由服务
  54. List<GatewayApiRoute> results = jdbcTemplate.query(
  55. "select * from gateway_api_route where enabled = true ",
  56. new BeanPropertyRowMapper<>(GatewayApiRoute.class));
  57. for (GatewayApiRoute result : results) {
  58. if (StringUtils.isEmpty(result.getPath()) ) {
  59. continue;
  60. }
  61. if (StringUtils.isEmpty(result.getServiceId()) && StringUtils.isEmpty(result.getUrl())) {
  62. continue;
  63. }
  64. ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
  65. try {
  66. BeanUtils.copyProperties(result, zuulRoute);
  67. } catch (Exception e) {
  68. e.printStackTrace();
  69. }
  70. routes.put(zuulRoute.getPath(), zuulRoute);
  71. }
  72. return routes;
  73. }
  74. }
  1. public class GatewayApiRoute {
  2. private String id;
  3. private String path;
  4. private String serviceId;
  5. private String url;
  6. private boolean stripPrefix = true;
  7. private Boolean retryable;
  8. private Boolean enabled;
  9. }
  1. @Configuration
  2. public class DynamicRouteConfiguration {
  3. @Autowired
  4. private ZuulProperties zuulProperties;
  5. @Autowired
  6. private ServerProperties server;
  7. @Autowired
  8. private JdbcTemplate jdbcTemplate;
  9. @Bean
  10. public DynamicRouteLocator routeLocator() {
  11. DynamicRouteLocator routeLocator = new DynamicRouteLocator(
  12. this.server.getServletPrefix(), this.zuulProperties);
  13. routeLocator.setJdbcTemplate(jdbcTemplate);
  14. return routeLocator;
  15. }
  16. }

定时刷新路由信息

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;
  3. import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
  4. import org.springframework.context.ApplicationEventPublisher;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.scheduling.annotation.EnableScheduling;
  7. import org.springframework.scheduling.annotation.Scheduled;
  8. import org.springframework.stereotype.Component;
  9. @Component
  10. @Configuration
  11. @EnableScheduling
  12. public class RefreshRouteTask {
  13. @Autowired
  14. private ApplicationEventPublisher publisher;
  15. @Autowired
  16. private RouteLocator routeLocator;
  17. @Scheduled(fixedRate = 5000)
  18. private void refreshRoute() {
  19. System.out.println("定时刷新路由表");
  20. RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
  21. publisher.publishEvent(routesRefreshedEvent);
  22. }
  23. }