Zuul源码解析(一)

news2024/11/15 12:12:10

说在前面

我们公司有一个线上服务报错通知群,经常报网关服务的一个 EOFException 异常。这个异常报出来好久了,如下图所示,艾特相关的人也不去处理,大概是不重要异常吧,反正看样子是不影响线上核心业务流程。

然后我上级让我优化下这类日志打印,把无法解析的参数,把原url打印出来,另外把这类日志等级调整成 warn,不需要处理的就不要用 error 一直报警了。

在根据打印的日志堆栈信息中,可以看到这些日志主要是 Zuul 默认的 SendErrorFilter 打印的,在思考怎么优化这类日志的同时也冒出很多对 Zuul 的疑问:

  1. 既然这些日志是 Zuul 框架打印的 error 级别的日志,并且 error 已经是最高级别了,我还能调整级别?我又如何我新增打印参数如 url,请求体等信息?
  2. 堆栈链路中的 ZuulServlet.service() 方法是啥?
  3. Zuul 网关请求和 DispatcherServlet 有什么联系?它们之间又是如何协作的?
  4. Zuul 有一堆的 Filter,它们是如何串联起来的?它是如何路由发起请求的?

当时脑子里真的是一堆的疑问,它驱使着我翻看 zuul 源码的好奇心愈发增强。

于是就有了这边关于 Zuul 网关的源码解析。

下面,我将从我当时对 Zuul 的疑问点为切入点,是如何一步步带着问题去阅读源码,把整个流程给弄明白的思路给你们讲清楚。也许,我当时的疑问也正式你的疑问呢,希望可以帮助有需要的人。

Zuul 源码解析

Zuul 的本质就是 Servlet 和 Filter 链。Servlet 是业务入口以及控制整个流程,Filter 链处理整个请求的一些逻辑,包括前置处理,转发路由,后置处理返回响应等等

Zuul 的核心流程

Zuul 的核心流程主要封装在 ZuulServlet 中的 service() 方法中

public class ZuulServlet extends HttpServlet {
	// 省略了部分代码方法和属性
  	// ...
    private ZuulRunner zuulRunner;

    @Override
    public void service(ServletRequest servletRequest,ServletResponse servletResponse){
        try {
            // 初始化 RequestContext,该 RequestContext 是贯穿 Zuul 处理请求的生命周期的
          	// 主要是暂存 request,response,异常信息等处理请求过程中中间结果
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);;

            try {
                // 处理全部 pre 类型的 Filter
                preRoute();
            } catch (ZuulException e) {
              	// 抛异常时,在在 error() 方法中存储该异常到 RequestContext 上下文中,后续会用到的
                // RequestContext.getCurrentContext().setThrowable(e);
                // 接着再处理所有 error 类型 Filter
                error(e);
                // 接着在执行所有 post 类型的 Filter
                postRoute();
              	// 就这样,一个请求在报错下经过 Zuul 的生命周期就结束了
                return;
            }
            try {
              	// preRoute() 没有异常,则执行 route 类型的 Filter
                route();
            } catch (ZuulException e) {
              	// 同上备注
                error(e);
                postRoute();
                return;
            }
            try {
              	// route() 没有异常,则执行 所有 post 类型 Filter
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }
  // ...
}

preRoute() 方法中,处理 Filter 的逻辑是委托给 zuulRunner 去完成的,其他两个 error()postRoute() 同理。

	// ZuulServlet#preRoute()
	void preRoute() throws ZuulException {
        zuulRunner.preRoute();
    }

可以理解 ZuulServlet 是入口,是控制执行流程的,类似于 DisPatcherServlet。而干实事的是 ZuulRunner,ZuulRunner 的主要职责是主要有两个:

  1. 初始化 requests 和 responses 到 RequestContext 中
	// ZuulRunner#init()
	public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {

        RequestContext ctx = RequestContext.getCurrentContext();
        if (bufferRequests) {
            ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
        } else {
            ctx.setRequest(servletRequest);
        }

        ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
    }

2)封装 preRoute(), route(), postRoute()error() 的 FilterProcessor 调用

	// 如 ZuulRunner#postRoute()
	public void postRoute() throws ZuulException {
      	// 看 FilterProcessor(拦截器处理器)名字就知道这是应用了典型的拦截器模式
        FilterProcessor.getInstance().postRoute();
    }

再看看 FilterProcessor 的处理逻辑

public class FilterProcessor {
  //...
  // 执行所有 "post" filters. 
  public void postRoute() throws ZuulException {
        try {
          	// runFilters 方法只有一个参数
          	// 它里面主要做的就是通过查找所有 post 类型的 FIlter,然后根据每个 ZuulFilter 的 Order 进行排序
          	// 排完序之后,执行 shouldFilter(),满足条件的话,在执行 Filter 的实际逻辑方法 run()
            runFilters("post");
        } catch (ZuulException e) {// 其中一个 Filter 出错则抛出异常
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
        }
    }

    // 执行所有 "error" filters. 
    public void error() {
        try {
            runFilters("error");
        } catch (Throwable e) {// 其中一个 Filter 出错则抛出异常
          	// 注意这里,在 FilterProcessor 执行所有 error 类型的 Filter 时,只会打印该异常,不会再抛出。
          	// 文章开头中的日志截图中,其中有一条就是在这里打印的。
            logger.error(e.getMessage(), e);
        }
    }

   // 执行所有 "route" filters. 
    public void route() throws ZuulException {
        try {
            runFilters("route");
        } catch (ZuulException e) {// 其中一个 Filter 出错则抛出异常
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
        }
    }

    // 执行所有 "pre" filters
    public void preRoute() throws ZuulException {
        try {
            runFilters("pre");
        } catch (ZuulException e) {// 其中一个 Filter 出错则抛出异常
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
        }
    }
  
 	// ...
}

如上源代码备注所示,流程已经很清晰了。说实话,由于自己当时对 Zuul 的基础知识不够了解,当时看到这个流程就在想,Zuul 有哪些默认的 Filter 呢,发起服务调用是在哪里,route() 方法吗,意思是发送服务调用也是在 Filter 中去完成?

RouteLocator 路由定位器的职责。

在讲 Zuul 默认的 Filter 之前,我觉得有必要先了解 ZuulProperties,RouteLocator 等基础设施类。

ZuulProperties

@ConfigurationProperties("zuul")
public class ZuulProperties {

	private String prefix = "";

	private Boolean retryable = false;

	/**
	 * Map of route names to properties.
	 */
	private Map<String, ZuulRoute> routes = new LinkedHashMap<>();
  
 	// ...
  	public static class ZuulRoute {
		private String id;

		/**
		 * The path (pattern) for the route, e.g. /foo/**.
		 */
		private String path;

		// 可以理解是服务名称
		private String serviceId;

		// 路由转发 url 全路径,它和 serviceId 是二选一的。
      	// 我们服务中没有使用它
		private String url;
      	// ...
      	// 先看着,后面会调用
		public String getLocation() {
			if (StringUtils.hasText(this.url)) {
              	// url 不为空,返回 url
				return this.url;
			}
          	// 否则,返回 serviceId
			return this.serviceId;
		}
    }
}

也许上面配置类中属性看的有点抽象的话,不妨看看如下我目前所在公司的路由配置实例:

#路由规则配置
zuul.routes.usercenter.path = /usercenter/**
zuul.routes.usercenter.serviceId = USERCENTER
zuul.routes.platform.path = /platform/**
zuul.routes.platform.serviceId = PLATFORM
zuul.routes.paycenter.path = /paycenter/**
zuul.routes.paycenter.serviceId = paycenter
...

使用者怎么理解呢?比如一个常规请求:https://网关域名/usercenter/user/detail/1

Zuul 就会取 /usercenter/user/detail/1 去匹配如上配置中的 path 属性,发现 /usercenter/** 符合该路径的匹配规则,就找到对应的 serviceId = USERCENTER,那么就会路由到 USERCENTER 这个服务的 /user/detail/1 接口。

RouteLocator

// 路由定位器
// 怎么理解呢,主要是做什么的呢,看接口就很直观了
public interface RouteLocator {
  
	Collection<String> getIgnoredPaths();
  	// 获取所有的路由配置
    // 路由定位器和其他组件的交互,是最终把定位的 Routes 以 list 的方式提供出去。后面源码中很多地方都会调用
    // 如上面的配置例子中:zuul.routes.usercenter.path = /usercenter/**
	//				    zuul.routes.usercenter.serviceId = USERCENTER
  	// 					这就组成一个 Route 实体
	List<Route> getRoutes();
  	// 根据请求 path,比如如上例子中的 /usercenter/user/detail/1,匹配到一个 Route
  	// Route 这个类与 ZuulProperties 的内部类 ZuulRoute 非常的相似,都是描述路由的一个类
  	// 可以理解 ZuulRoute 的配置用的实体路由类, Route 是业务中使用的路由类
	Route getMatchingRoute(String path);

}

RouteLocator 有两个实现类,分别是 SimpleRouteLocator 和 DiscoveryClientRouteLocator

SimpleRouteLocator

SimpleRouteLocator(简单路由定位器) 我认为是最核心的一个路由定位器类。它主要负责管理 ZuulProperties 配置的 Route。

这里我也是重点关注它的 getRoutes()getMatchingRoute(String path) 两个方法。因为我觉得了解了这两个方法的实现,对后续阅读 理解 zuul 的核源码非常有必要的。

// 该类的重要属性如下:
public class SimpleRouteLocator implements RouteLocator, Ordered {
    // ...
    // 配置文件中的路由信息
	private ZuulProperties properties;
	private PathMatcher pathMatcher = new AntPathMatcher();

    // 可以理解是 ZuulProperties 的一个路径模板 path -> ZuulRoute 的 map 缓存
  	// 目的是避免重复在 ZuulProperties 中计算得到 Route 列表
	private AtomicReference<Map<String, ZuulRoute>> routes = new AtomicReference<>();
  // ...
}
getRoutes()

获取所有的路由

@Override
public List<Route> getRoutes() {
	List<Route> values = new ArrayList<>();
  	// 这几行代码很简单,没啥好说的。我们主要关注下 getRoutesMap() 方法
	for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
		ZuulRoute route = entry.getValue();
		String path = route.getPath();
		values.add(getRoute(route, path));
	}
	return values;
}

getRoutesMap() 方法:

// 这个方法可以理解就是初始化上面的 map 缓存(路径模板path -> ZuulRoute)
protected Map<String, ZuulRoute> getRoutesMap() {
  	// 为空,则初始化
	if (this.routes.get() == null) {
        // 重点关注下 locateRoutes() 方法
		this.routes.set(locateRoutes());
	}
	return this.routes.get();
}

locateRoutes() 方法:

// 该方法是 protected ,一般情况下要自定义 RouteLocator 时,会重写该方法,比如我需要从数据库中加载 Route
protected Map<String, ZuulRoute> locateRoutes() {
	LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
  	// 看这里,就是遍历了 ZuulProperties 中的 routes 属性
	for (ZuulRoute route : this.properties.getRoutes().values()) {
		routesMap.put(route.getPath(), route);
	}
	return routesMap;
}
getMatchingRoute()

根据请求 url(requestUri)匹配 Route

@Override
public Route getMatchingRoute(final String path) {
	// 直接调用下方这个方法
	return getSimpleMatchingRoute(path);

}

protected Route getSimpleMatchingRoute(final String path) {
	// ...
	// This is called for the initialization done in getRoutesMap()
	getRoutesMap();
	// ...
	String adjustedPath = adjustPath(path);
	// 根据 requestUri 返回一个 ZuulRoute
  	// 具体实现,往下看
	ZuulRoute route = getZuulRoute(adjustedPath);
  	// 前面说到业务中使用的都是 Route 实体,并不是 ZuulRoute
  	// 调用 getRoute() 方法就是把 ZuulRoute 转换加工成 Route 返回
	return getRoute(route, adjustedPath);
}

protected ZuulRoute getZuulRoute(String adjustedPath) {
	if (!matchesIgnoredPatterns(adjustedPath)) {
      	// getRoutesMap() 获取前面缓存好的 route 配置 Map
      	// 遍历,根据模板 path 模糊匹配 requestUri
		for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
			String pattern = entry.getKey();
          	// 重点在这里,使用 pathMatcher 来匹配一个 ZuulRoute
			if (this.pathMatcher.match(pattern, adjustedPath)) {
              	// 匹配则返回一个 ZuulRoute
				return entry.getValue();
			}
		}
	}
	return null;
}

// 刷新 routes 缓存时调用的,咋暂时不管
protected void doRefresh() {
	this.routes.set(locateRoutes());
}

DiscoveryClientRouteLocator

我习惯叫这个 RouteLocator 为服务发现路由定位器。DiscoveryClientRouteLocator 继承自 SimpleRouteLocator,它的 getRoutes() 方法中,除了获取 ZuulProperties 配置的 zuulRoute 外,还包括从注册中心拉取的动态路由配置。

怎么理解它呢?比如:你部署上线一个新应用服务名称为 ordercenter,然后你不再需要在网关服务中新增 apollo 配置,你就可以向网关发起请求,类似 https://网关域名/ordercenter/order/detail/1001 的方式,网关 Zuul 也会根据你的意愿转发请求到新服务 ordercenter。因为 DiscoveryClientRouteLocator 它会动态拉取新的服务路由配置,动态刷新本地路由集合。

接下,我们来看它是如何实现的。.

public class DiscoveryClientRouteLocator extends SimpleRouteLocator
      implements RefreshableRouteLocator {
   // 服务发现 client
   private DiscoveryClient discovery;

   private ZuulProperties properties;

   private ServiceRouteMapper serviceRouteMapper;

   public void addRoute(String path, String location) {
      this.properties.getRoutes().put(path, new ZuulRoute(path, location));
      refresh();
   }
   
   // ...
   // 返回 ZuulProperties 中的路由和服务发现拉取的路由
   @Override
   protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
      LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
      // 把 ZuulProperties 中的路由配置加进来
      routesMap.putAll(super.locateRoutes());
      if (this.discovery != null) {
      	 // 静态服务 map,也就相当于遍历 ZuulProperties 中的路由配置,把他添加到 staticServices map 中去
         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);
            }
         }
         /**
          * 添加服务发现中的路由
          */
         // 通过 DiscoveryClient 获取所有的服务名称(比如 usercenter,ordercenter 等)
         List<String> services = this.discovery.getServices();
         // ...
         // 遍历获取到的所有的服务名称
         for (String serviceId : services) {
            // staticServices 静态服务配置是否已经配置了
            if (staticServices.containsKey(serviceId)
                  && staticServices.get(serviceId).getUrl() == null) {
               
               ZuulRoute staticRoute = staticServices.get(serviceId);
               if (!StringUtils.hasText(staticRoute.getLocation())) {
               	  // 赋值 location
                  staticRoute.setLocation(serviceId);
               }
            }
            // 如果静态服务配置没有配置
            if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
                  && !routesMap.containsKey(key)) {
               // 那么它添加到 routesMap 中去。
               routesMap.put(key, new ZuulRoute(key, serviceId));
            }
         }
      }
      // ...
      // 该方法返回值 map
      LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
      // 遍历 routesMap
      for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
         String path = entry.getKey();
         // ...
         values.put(path, entry.getValue());
      }
      return values;
   }

   // 这个方法一般是监听器 Listener 监听到注册中心更新事件时出发本地路由的更新
   @Override
   public void refresh() {
      doRefresh();
   }

}

CompositeRouteLocator

这有点类似于 WebMvcConfigurerComposite,CompositeRouteLocator 就是一个集成了所有 RouteLocator 实现的一个组合类

所以在后面源码中,出现 RouteLocator,都是 CompositeRouteLocator 的实现。

public class CompositeRouteLocator implements RefreshableRouteLocator {
  	// 默认情况下,zuul 的自动配置中会注册默认的 DiscoveryClientRouteLocator 和 SimpleRouteLocator 实现 bean
	private final Collection<? extends RouteLocator> routeLocators;
	private ArrayList<RouteLocator> rl;

	public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {
		Assert.notNull(routeLocators, "'routeLocators' must not be null");
		rl = new ArrayList<>(routeLocators);
		AnnotationAwareOrderComparator.sort(rl);
		this.routeLocators = rl;
	}

  	// 这个方法组合了所有 RouteLocator 的实现(包括 DiscoveryClientRouteLocator 和 SimpleRouteLocator)
	@Override
	public List<Route> getRoutes() {
      	
		List<Route> route = new ArrayList<>();
      	// 编译每个 RouteLocator,然后调用每个 RouteLocator 的 getRoutes()方法聚合全部 Route
		for (RouteLocator locator : routeLocators) {
          	
			route.addAll(locator.getRoutes());
		}
		return route;
	}

	@Override
	public Route getMatchingRoute(String path) {
		for (RouteLocator locator : routeLocators) {
			Route route = locator.getMatchingRoute(path);
			if (route != null) {
				return route;
			}
		}
		return null;
	}

	@Override
	public void refresh() {
		for (RouteLocator locator : routeLocators) {
			if (locator instanceof RefreshableRouteLocator) {
				((RefreshableRouteLocator) locator).refresh();
			}
		}
	}
}

CompositeRouteLocator#getRoutes() 方法这里其实我存在一个疑问的,我抛出来给你们:

SimpleRouteLocator#getRoutes() 包含了 ZuulProperties 的路由,而 DiscoveryClientRouteLocator 继承了 SimpleRouteLocator,它的 getRoutes() 包含了 ZuulProperties 的路由,然后 CompositeRouteLocator#getRoutes() 方法把这两个实现获取的 Route 集合通过 addAll() 的的形式再组合成一个新的集合,那这样的 ZuulProperties 的配置路由不就重复了么,尽管是不影响路由匹配,你们觉得呢?

RefreshableRouteLocator

当路由发生改变时,route locator 可以刷新本地缓存的一个接口,目前 DiscoveryClientRouteLocator 实现了该接口。

该接口就一个方法:

public interface RefreshableRouteLocator extends RouteLocator {

	void refresh();

}

当 Listener 监听到注册中心更新事件时会触发本地路由的更新

https://qncdn.wanshifu.com/d12824966d80c37c7b8e1570c160b83f

还不知道这行代码的 this.zuulHandlerMapping.setDirty(true); 先往下看,后面就知道了。

Zuul 默认的 filter

再回到前面埋的坑,Zuul 有哪些默认的 Filter 呢,发送服务调用也是在 Filter 中去完成么,答案:是的

Pre 过滤器

Pre 类型的过滤器主要是在路由转发请求前的前置处理,比如对 request 的包装,初始化真正转发时需要的一些参数,匹配 Route 等等

ServletDetectionFilter(了解即可)
public class ServletDetectionFilter extends ZuulFilter {
    // ... 
    @Override
	public int filterOrder() {
        // 优先级为-3,数字越小,越先执行
		return SERVLET_DETECTION_FILTER_ORDER;
    }
    
	@Override
	public boolean shouldFilter() {
		return true; 
	}

	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
        // 阅读源码过程中,存在过这样一个疑问:执行到这里时,request 设置到RequestContext 中了么? 
      	// ZuulRunner#init() 中已经赋值了
		HttpServletRequest request = ctx.getRequest();
		if (!(request instanceof HttpServletRequestWrapper) 
				&& isDispatcherServletRequest(request)) {
            // 如果是 DispatcherServlet 过来的,则存储该标识
            // 一般的网关请求都是 DispatcherServlet 进来的,我后面会说到。
			ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);
		} else {
			ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);
		}

		return null;
	}	 	
    // ... 
}

检测当前请求是通过 Spring 的 DispatcherServlet 处理运行的,还是通过 ZuulServlet 来处理运行的。它的检测结果会以布尔类型保存在当前请求上下文的 isDispatcherServletRequest 参数中,这样后续的过滤器中,我们就可以通过 RequestUtils.isDispatcherServletRequest() 和 ~方法来判断请求处理的源头,以实现后续不同的处理机制

Servlet30WrapperFilter(了解即可)

优先级 -2,ServletDetectionFilter 之后执行

	@Override
	public boolean shouldFilter() {
		return true;


	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest request = ctx.getRequest();
		if (request instanceof HttpServletRequestWrapper) {
			request = (HttpServletRequest) ReflectionUtils.getField(this.requestField,
					request);
			ctx.setRequest(new Servlet30RequestWrapper(request));
		}
        // 是否是 DispatcherServlet 过来的
		else if (RequestUtils.isDispatcherServletRequest()) {
			// 包装原始 request
			ctx.setRequest(new Servlet30RequestWrapper(request));
		}
		return null;
	}

讲到这里,觉得有必要说下 Zuul 的 com.netflix.zuul.http.HttpServletRequestWrapper

HttpServletRequestWrapper 是对原始 HttpServletRequest 的一个包装。我们知道 HttpServletRequest 的 getInputStream() 方法是不可重复读的。

所以 Zuul 就对它做了一个包装的增强,使得 getInputStream() 可以重复读。原理也很简单,就是本地缓存。

而 Servlet30RequestWrapper 也是继承了 com.netflix.zuul.http.HttpServletRequestWrapper 的,那就相当于在这个过滤器中存储到 RequestContext 中的 request 是可以重复读取 getInputStream() 的。

FormBodyWrapperFilter(了解即可)

它的执行顺序为 -1,是第三个执行的过滤器。该过滤器仅对两类请求生效,第一类是 Context-Type 为 application/x-www-form-urlencoded 的请求,第二类是 Context-Type 为 multipart/form-data 并且是由 Spring 的 DispatcherServlet 处理的请求(用到了ServletDetectionFilter 的处理结果)。而该过滤器的主要目的是将符合要求的请求体包装成 FormBodyRequestWrapper 对象。

PS: FormBodyRequestWrapper 是继承了 Servlet30RequestWrapper。

DebugFilter(了解即可)

它的执行顺序为 1,是第四个执行的过滤器。该过滤器会根据配置参数 zuul.debug.request 和请求中的 debug 参数来决定是否执行过滤器中的操作。而它的具体操作内容是将当前请求上下文中的 debugRouting 和 debugRequest 参数设置为 true。由于在同一个请求的不同生命周期都可以访问到这二个值,所以我们在后续的各个过滤器中可以利用这二个值来定义一些 debug 信息,这样当线上环境出现问题的时候,可以通过参数的方式来激活这些 debug 信息以帮助分析问题,另外,对于请求参数中的 debug 参数,我们可以通过zuul.debug.parameter 来进行自定义。

PreDecorationFilter(较为重要)

执行顺序是 5,是 pre 阶段最后被执行的过滤器,主要是给当前请求上下文中赋值 forward.toserviceId 参数,这两个参数在后面转发和发起调用具体服务时会用到。

public class PreDecorationFilter extends ZuulFilter {
    @Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
		return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
				&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId
	}
    
    @Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
		// 请求 uri 匹配获取 Route
        Route route = this.routeLocator.getMatchingRoute(requestURI);
		if (route != null) {
            // 还记得吗,前面 ZuulProperties 有介绍过这个方法。
          	// 所以这里获取的 location 是 Route 配置中的 url or serviceId。
			String location = route.getLocation();
            // locationy 以 http 或 https 开头
            if (location.startsWith(HTTP_SCHEME+":") || location.startsWith(HTTPS_SCHEME+":")) {
             		// 存储在ctx 上下文中,SimpleHostRoutingFilter 会使用使用 该routeHost 发起http 请求完成路由转发
					ctx.setRouteHost(getUrl(location));
					ctx.addOriginResponseHeader(SERVICE_HEADER, location);
				}
            // 如果 location 是以 "forward:" 开头,存储对应的属性,SendForwardFilter 会使用到
			else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {
				ctx.set(FORWARD_TO_KEY,
                        StringUtils.cleanPath(location.substring(FORWARD_LOCATION_PREFIX.length()) + route.getPath()));
				ctx.setRouteHost(null);
				return null;
			}
			else {
				// 这个分支,是我们公司主要跑的这种情况,set serviceId,支持服务发现,负载均衡等
                // RibbonRoutingFilter
				ctx.set(SERVICE_ID_KEY, location);
				ctx.setRouteHost(null);
				ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
			}
        }
    }
}

Route 过滤器

这类型就是路由过滤去,比如请求转发服务。

RibbonRoutingFilter

该过滤器只对请求上下文中存在 serviceId 参数的请求进行处理,即只对通过 serviceId 配置路由规则的请求生效

public class RibbonRoutingFilter extends ZuulFilter {

	@Override
	public String filterType() {
		return ROUTE_TYPE;
	}

    // order 值为 5,是 Route 类型执行的第一个 Filter
	@Override
	public int filterOrder() {
		return RIBBON_ROUTING_FILTER_ORDER;
	}

	@Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
        // 路由配置中,url 为空,serviceId 不为空时满足条件
		return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
				&& ctx.sendZuulResponse());
	}

	@Override
	public Object run() {
		RequestContext context = RequestContext.getCurrentContext();
		this.helper.addIgnoredHeaders();
		try {
            // 封装 ribbon 上下文
			RibbonCommandContext commandContext = buildCommandContext(context);
            // 包装请求,发起向服务名称为 serviceId的请求调用,完成网关的转发功能
			ClientHttpResponse response = forward(commandContext);
            // 暂存请求结果到 context 中
			setResponse(response);
			return response;
		}
		catch (ZuulException ex) {
			throw new ZuulRuntimeException(ex);
		}
		catch (Exception ex) {
			throw new ZuulRuntimeException(ex);
		}
	}

}
SimpleHostRoutingFilter

该过滤器只对请求上下文存在 routeHost 参数的请求进行处理,即只对通过 url 配置路由规则的请求生效

public class SimpleHostRoutingFilter extends ZuulFilter {
    @Override
	public String filterType() {
		return ROUTE_TYPE;
	}

    // order 为 100,在 RibbonRoutingFilter 之后
	@Override
	public int filterOrder() {
		return SIMPLE_HOST_ROUTING_FILTER_ORDER;
	}

	@Override
	public boolean shouldFilter() {
        // RouteHost 不为空,
      	// 也就是当路由配置中,serviceid 为空,url 不为空时(在 PreDecorationFilter 中赋值的 RouteHost)
		return RequestContext.getCurrentContext().getRouteHost() != null
				&& RequestContext.getCurrentContext().sendZuulResponse();
	}

	@Override
	public Object run() {
		RequestContext context = RequestContext.getCurrentContext();
		HttpServletRequest request = context.getRequest();
		// ...
		String uri = this.helper.buildZuulRequestURI(request);
		this.helper.addIgnoredHeaders();

		try {
            // 通过 HttpClient 发起实际请求,完成转发
			CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
					headers, params, requestEntity);
			setResponse(response);
		}
		catch (Exception ex) {
			throw new ZuulRuntimeException(ex);
		}
		return null;
	}
}
SendForwardFilter

该过滤器只对请求上下文中存在的 forward.do 参数进行处理请求,即用来处理路由规则中的 forward 本地跳转装配

public class SendForwardFilter extends ZuulFilter {
    @Override
	public String filterType() {
		return ROUTE_TYPE;
	}

    // order = 500,在 SimpleHostRoutingFilter 之后
    @Override
    public int filterOrder() {
    	return SEND_FORWARD_FILTER_ORDER;
    }
    
    @Override
    public boolean shouldFilter() {
    	RequestContext ctx = RequestContext.getCurrentContext();
        // PreDecorationFilter 中赋值的 FORWARD_TO_KEY 不为空
    	return ctx.containsKey(FORWARD_TO_KEY)
    			&& !ctx.getBoolean(SEND_FORWARD_FILTER_RAN, false);
    }
    
    @Override
    public Object run() {
    	try {
    		RequestContext ctx = RequestContext.getCurrentContext();
    		String path = (String) ctx.get(FORWARD_TO_KEY);
            // RequestDispatcher 服务端转发
    		RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
    		if (dispatcher != null) {
    			ctx.set(SEND_FORWARD_FILTER_RAN, true);
    			if (!ctx.getResponse().isCommitted()) {
    				dispatcher.forward(ctx.getRequest(), ctx.getResponse());
    				ctx.getResponse().flushBuffer();
    			}
    		}
    	}
    	catch (Exception ex) {
    		ReflectionUtils.rethrowRuntimeException(ex);
    	}
    	return null;
    }
}

post 过滤器

SendResponseFilter

当请求完成之后,在该过滤器中返回响应到客户端。

public class SendResponseFilter extends ZuulFilter {
    // ...
    @Override
	public String filterType() {
		return POST_TYPE;
	}

    // order = 1000
	@Override
	public int filterOrder() {
		return SEND_RESPONSE_FILTER_ORDER;
	}

	@Override
	public boolean shouldFilter() {
		RequestContext context = RequestContext.getCurrentContext();
      	// 异常上下文为空,并且转发响应体不为空则执行该过滤器
		return context.getThrowable() == null
				&& (!context.getZuulResponseHeaders().isEmpty()
					|| context.getResponseDataStream() != null
					|| context.getResponseBody() != null);
	}

	@Override
	public Object run() {
		try {
          	// 添加响应请求头
			addResponseHeaders();
          	// 返回响应
			writeResponse();
		}
		catch (Exception ex) {
			ReflectionUtils.rethrowRuntimeException(ex);
		}
		return null;
	}
    // ...
}

error 过滤器

SendErrorFilter

pre Filterroute Filterpost Filter 在执行过程中,任何一个 Filter 发生异常,则都会进入该过滤器,该过滤器中,主要是转发到错误页面或者默认的 /error rest 接口中完成响应给客户端。我们可以通过配置禁掉该默认错误 Filter,通过自定义 Error Filter 来处理异常响应。

public class SendErrorFilter extends ZuulFilter {
@Override
	public String filterType() {
		return ERROR_TYPE;
	}

	@Override
	public int filterOrder() {
		return SEND_ERROR_FILTER_ORDER;
	}

	@Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
		// 上下文中的异常信息不为空则执行该过滤器
		return ctx.getThrowable() != null
				&& !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
	}

	@Override
	public Object run() {
		try {
			RequestContext ctx = RequestContext.getCurrentContext();
			ZuulException exception = findZuulException(ctx.getThrowable());
			HttpServletRequest request = ctx.getRequest();
          	// ...
			RequestDispatcher dispatcher = request.getRequestDispatcher(
					this.errorPath);
			if (dispatcher != null) {
				ctx.set(SEND_ERROR_FILTER_RAN, true);
				if (!ctx.getResponse().isCommitted()) {
					ctx.setResponseStatusCode(exception.nStatusCode);
                  	// 转发到 error path
					dispatcher.forward(request, ctx.getResponse());
				}
			}
		}
		catch (Exception ex) {
			ReflectionUtils.rethrowRuntimeException(ex);
		}
		return null;
	}
}

由于全文篇幅太长,我把 Zuul 的源码解析分成了两篇文章,感兴趣可前往 Zuul源码解析(二)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/453608.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FreeRTOS学习笔记(一)——初识FreeRTOS

FreeRTOS官网&#xff1a;FreeRTOS - 适用于具有物联网扩展功能的嵌入式系统的市场领先 RTOS&#xff08;实时操作系统&#xff09; FreeRTOS源码下载&#xff1a;FreeRTOS Real Time Kernel (RTOS) - Browse /FreeRTOS at SourceForge.net 目录 0x01 FreeRTOS编程风格 一…

用CentOS服务器自己搭建部署个Discuz论坛网站,网站搭建教程

Linux系统CentOS服务器使用堡塔搭建论坛网站全套教程。服务器大本营&#xff0c;技术文章内容集合站发车啦&#xff01; 操作系统&#xff1a;Centos 7.6 网站程序&#xff1a;Discuz-X3.4 前言 首先&#xff0c;搭建一个网站需要准备&#xff1a;服务器、域名、网站程序。 …

PWM控制直流电机

一&#xff0c;TB6612电机驱动模块 直流电机属于大功率器件&#xff0c;GPIO无法直接驱动&#xff0c;需要电机驱动模块配合&#xff0c;才能驱动直流电机. TB6612可以驱动2个直流电机。由IN1&#xff0c;IN2控制电机旋转方向&#xff0c;由PWM控制电机旋转速度。 二&#xf…

基于Oracle VM VirtualBox的ubuntu的安装

基于Oracle VM VirtualBox的ubuntu的安装 感谢詹老师的帮助使我得以完成本次安装&#xff0c;以下为本次安装的一个小小的记录。 目录 基于Oracle VM VirtualBox的ubuntu的安装Oracle VM VirtualBox的下载与安装ubuntu的下载Oracle VM VirtualBox下安装ubuntu安装 ROS Melodi…

GitHub 开源神器 Bark模型,让文本转语音更简单

今天跟大家分享一个文本转语音的开源模型&#xff1a;Bark Bark 是由Suno创建的基于转换器的文本到音频模型。Bark 可以生成高度逼真的多语言语音以及其他音频 - 包括音乐、背景噪音和简单的音效。该模型还可以产生非语言交流&#xff0c;如大笑、叹息和哭泣。 该项目刚开源不…

二叉树OJ题(C++实现)

文章目录 1.二叉树创建字符串2. 二叉树的最近公共祖先3.二叉搜索树与双向链表4.从前序与中序遍历序列构造二叉树 1.二叉树创建字符串 二叉树的层序遍历 OJ连接 主要思路是借助一个队列&#xff0c;将每一层的数据以size统计&#xff0c;当size为0时说明该层数据已经输入完&…

Unity Physics2D 2d物理引擎游戏 笔记

2d 材质 里面可以设置 摩擦力 和 弹力 Simulated&#xff1a;是否在当前的物理环境中模拟&#xff0c;取消勾选该框类似于Disable Rigidbody&#xff0c;但使用这个参数更加高效&#xff0c;因为Disable会销毁内部产生的GameObject&#xff0c;而取消勾选Simulated只是禁用。…

详解C语言string.h中常用的14个库函数(四)

本篇博客会讲解最后4个函数&#xff0c;分别是memset, memcpy, memmove, memcmp。这4个函数开头都是mem&#xff0c;即memory&#xff08;内存&#xff09;的缩写。 memset void * memset ( void * ptr, int value, size_t num );memset可以用来设置内存中的值。该函数可以把从…

深度学习实战——循环神经网络(RNN、LSTM、GRU)

忆如完整项目/代码详见github&#xff1a;https://github.com/yiru1225&#xff08;转载标明出处 勿白嫖 star for projects thanks&#xff09; 目录 系列文章目录 一、实验综述 1.实验工具及内容 2.实验数据 3.实验目标 4.实验步骤 二、循环神经网络综述 1.循环神经…

【数据结构】第五章 树与二叉树

文章目录 知识体系5.1 树的基本概念5.1.1 树的定义5.1.2 基本术语5.1.3 树的性质 5.2 二叉树的概念5.2.1 二叉树的定义和主要特性5.2.2 二叉树的存储结构 5.3 二叉树的遍历和线索二叉树5.3.1 二叉树的遍历5.3.2 线索二叉树 5.4 树、森林5.4.1 树的存储结构5.4.2 树、森林与二叉…

uniapp踩坑之项目:各端条件编译

在 HBuilderX 中&#xff0c;ctrlalt/ 即可生成正确注释&#xff08;js&#xff1a;// 注释、css&#xff1a;/* 注释 */、vue/nvue模板&#xff1a; &#xff09;。 #ifdef&#xff1a;if defined 仅在某平台存在#ifndef&#xff1a;if not defined 除了某平台均存在%PLATFORM…

ARM busybox 的移植实战2

一、busybox 源码分析1 1、源码目录梳理 2、整个程序入口的确认 (1) 分析一个程序&#xff0c;不管多庞大还是小&#xff0c;最好的路线都是 按照程序运行时的逻辑顺序来。所以找到一个程序的入口至关重要。 (2) 学 C 语言的时候都知道&#xff0c;程序的主函数 main 函数就是…

机器学习算法 随机森林

文章目录 一、概述1.1 集成学习1.2 决策树1.3 随机森林 二、Sklearn中的随机森林2.1 分类树API2.2 参数 2.2 回归树API2.2.1 重要参数 2.3 随机森林调参 三、总结 一、概述 1.1 集成学习 多个模型集成成为的模型叫做集成评估器&#xff08;ensemble estimator&#xff09;&am…

车载软件架构——闲聊几句AUTOSAR BSW(二)

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 我特别喜欢一个老话,来都来了。我觉得这就是松弛感,既然来了,就开心起来吧!松弛感来自于专注,焦虑不是靠克服的,是靠忘记的,当你很专注做一件事的时候…

HNCTF-re部分复现

目录 [HNCTF 2022 WEEK3]Help_Me! [HNCTF 2022 WEEK3]Whats 1n DLL? [HNCTF 2022 WEEK4]ez_maze 这几天在做HNCTF的week3&#xff0c;week4部分&#xff0c;学到了一些不知道的没接触过的东西&#xff0c;所以记录一下 [HNCTF 2022 WEEK3]Help_Me! 题目下载&#xff1a;下…

onnx笔记2:onnx操作实例

1. 介绍 本文以yolov5s模型,演示对yolov5s.onnx模型文件的读取,修改等操作 2. onnx操作 2.1 获取数据 (1) 案例1 :读取weights数据 比如获取yolov5s.onnx第一个Conv的weights数据。 点击左侧第一个Conv, 右侧INPUTS下面的W点开+号,可以看到该Conv的weight的name为m…

MySQL --- 主从复制、读写分离

一、MySQL主从复制 MySQL数据库默认是支持主从复制的&#xff0c;不需要借助于其他的技术&#xff0c;我们只需要在数据库中简单的配置即可。接下来&#xff0c;我们就从以下的几个方面&#xff0c;来介绍一下主从复制 1.1、介绍 MySQL主从复制是一个异步的复制过程&#xff0c…

linux 安装 oracle 11g

linux 安装 oracle 11g 1、下载oracle 11g (11.2.0.1.0)1.1、Oracle Database 11.2.0.1.01.2、Oracle Database Grid Infrastructure 11.2.0.1.01.3、客户端 2、安装文档3、安装前准备3.1、建立用户和用户组3.2、sysctl3.3、security limits3.4、其他设置3.5、创建安装目录3.6、…

SpringBootWeb请求响应

目录 前言 1. 请求 1.1 Postman 1.1.1 介绍 1.1.2 安装 1.2 简单参数 1.2.1 原始方式 1.2.2 SpringBoot方式 1.2.3 参数名不一致 小结 1.3 实体参数 1.3.1 简单实体对象 1.3.2 复杂实体对象 1.4 数组集合参数 1.4.1 数组 1.4.2 集合 1.5 日期参数 1.6 JSON参…

液压轴位置闭环控制(比例伺服阀应用)

液压阀的基础知识请参看下面的博客文章: PLC液压控制系列之比例流量阀结构分析_RXXW_Dor的博客-CSDN博客比例流量阀液压同步控制的PID闭环调节可以参看下面这篇博文三菱FX3U-PLC 前馈+PID闭环调节实现液压同步控制(比例换向阀)_RXXW_Dor的博客-CSDN博客液压控制系统在工业现…