zuul源码(2)

路由

路由是网关的核心功能,既然在spring的框架下,那就要按Spring的规矩来。

路由规则类:org.springframework.cloud.netflix.zuul.filters.Route 维护这以下信息:

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;

路由规则维护:RouteLocator

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);

}

类结构如下:
367180-20181118171140634-97966812.png

自动配置代码:

@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把注册中心的信息捞过来直接做映射成为路由规则列表。具体代码写的也有差点意思的,比如下图:
367180-20181118171235584-1906587313.png

首先有ZuulController和ZuulHandlerMapping,请求进来先在ZuulHandlerMapping里的列表上找有没有,如果有就把请求丢给ZuulController处理。所以里面一定维护这个一个<path,ZuulController>这么个map。这个匹配规则哪里来呢,一般我们认为是配置,但这里用了spring cloud,它加了注册的微服务动态加入匹配规则的逻辑,也就是下面的DiscoveryClientRouteLocator。
ZuulHandlerMapping的lookupHandler方法:

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方法,就可以重新在内存里拿注册中心可能同步过来的新的信息。

注意ZuulProxyAutoConfiguration中ZuulDiscoveryRefreshListener的onApplicationEvent方法:

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方法:

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;
}
### Zuul Forward 功能的源码分析 Zuul 是 Netflix 开发的一个基于 JVM 的路由器和服务端负载均衡器,广泛应用于微服务架构中的网关层。其核心功能之一是 `forward` 转发机制,允许请求被转发到指定的服务路径或其他内部资源。 #### 1. **ForwardFilter 实现** Zuul 中的 `forward` 功能主要通过 `org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter` 和其他相关过滤器来完成。以下是关键部分的实现逻辑: - 当前版本中,`RibbonRoutingFilter` 类负责处理 HTTP 请求并将其转发至目标服务实例。 - 如果配置了 `zuul.routes.<serviceId>.url` 或者设置了 `zuul.forward.to` 属性,则会触发该过滤器的工作流程[^1]。 ```java public class RibbonRoutingFilter extends ZonedAwareLoadBalancer<Server> implements ZuulFilter { private final RestTemplate restTemplate; @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); // 构建目标 URL 并执行转发操作 String serviceId = (String) ctx.get("serviceId"); URI endpointUri = this.determineEndpoint(serviceId); try { ClientHttpResponse response = executeRequest(request, endpointUri); return getResponse(response); } catch (Exception ex) { throw new ZuulException(ex.getMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null); } } protected URI determineEndpoint(String serviceId) { ServiceInstance instance = loadBalancer.choose(serviceId); if (instance != null) { return UriComponentsBuilder.fromHttpUrl(instance.getUri().toString()).build(true).toUri(); } return null; } } ``` 上述代码片段展示了如何利用 Ribbon 来选择具体的目标服务器地址,并构建最终的转发请求链接[^4]。 --- #### 2. **GitHub 源码位置** Zuul 的官方 GitHub 地址位于以下仓库: [Zuul 官方项目](https://github.com/Netflix/zuul) 对于 Spring Cloud 提供的封装版 Zuul(即我们通常使用的版本),可以访问以下仓库获取完整的源码实现: [Spring Cloud Netflix Zuul](https://github.com/spring-cloud/spring-cloud-netflix/tree/master/spring-cloud-netflix-zuul)[^5] 具体的 `forward` 功能实现在以下几个模块中: - `spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java` - `spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/web/ZuulHandlerMapping.java` 这些文件定义了路由匹配规则以及实际的转发行为。 --- #### 3. **示例配置与测试方法** 为了验证 `forward` 功能是否正常工作,可以通过以下方式设置路由规则: ```yaml zuul: routes: user-service: path: /users/** url: http://localhost:8081/ stripPrefix: false ``` 或者直接使用内置的 `forward` 参数: ```yaml zuul: routes: internal-resource: path: /internal/** forward: /local/resource ``` 以上配置表示当客户端访问 `/internal/*` 路径时,Zuul 将自动将请求转发到本地应用下的 `/local/resource` 接口[^3]。 --- #### 4. **注意事项** 在调试或开发过程中需要注意以下几点: - 确保依赖版本一致,推荐使用 Spring Cloud Finchley.SR1 及对应的 Spring Boot 版本 2.x[^2]。 - 配置文件中的属性名称区分大小写,请严格按照文档说明书写。 - 若涉及跨域问题,需额外启用 CORS 支持以避免浏览器拦截请求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值