前言
在上一章节我们大致了解了Zuul的Filter的执行流程和核心的Filter,这一章节我们消息分析一下Zuul的自动配置,以及每个Filter的实现细节,这需要你有一定的耐心<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">
一.Zuul的自动配置
1.EnableZuulProxy
开启zuul服务
我们在使用Zuul的时候需要在启动类贴上@EnableZuulProxy注解,我们就从这个注解入手分析Zuul,首先打开注解的源码看一下
/**
* Sets up a Zuul server endpoint and installs some reverse proxy filters in it, so it can
* forward requests to backend servers. The backends can be registered manually through
* configuration or via DiscoveryClient.
*
* @see EnableZuulServer for how to get a Zuul server without any proxying
*/
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
EnableZuulProxy
的注释告诉我们,这里设设置Zuul服务器端点,和安装了一些反向代理过滤器,通过这些过滤器它可以转发请求到后端服务器,可以通过配置或通过DiscoveryClient手动注册后端服务(服务发现)
不过这里@Import
导入了一个配类 ZuulProxyMarkerConfiguration
,这个配置在干嘛呢?看一下源码
/**
负责添加标记bean以触发 {@link ZuulProxyAutoConfiguration}的激活
* Responsible for adding in a marker bean to trigger activation of
* {@link ZuulProxyAutoConfiguration}
*
* @author Biju Kunjummen
*/
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
翻译:“Responsible for adding in a marker bean to trigger activation of
- {@link ZuulProxyAutoConfiguration}”
这个类的作用就是用来激活ZuulProxyAutoConfiguration
Zuul的自动配置的
它在负责添加标记bean以触发激活 ZuulProxyAutoConfiguration 这个类,研究过springboot自动配置的同学就会知道 ,SpringBoot 中会有大量的 xxxAutoConfiguration 自动配置的类会在应用启动的过程中被激活实现自动装配,从而节省了我们很多的配置
2.Zuul
自动配置ZuulProxyAutoConfiguration
ZuulProxyAutoConfiguration
是zuul比较核心的一个自动配置类,很多的组件都是在这个类中注册的,该自动配置类在 spring-cloud-netflix-zuul.xx.RELEASE
包的spring.factories文件中有定义,随着SpringBoot启动该配置类被注册到Spring容器中(不理解的可以先了解一下SpringBoot自动配置)
自动配置类 ZuulProxyAutoConfiguration
需要通过ZuulProxyMarkerConfiguration
来激活才可能被注册到容器中,下面是它的源码
@Configuration
//这里引入了几种客户端配置
@Import({
RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
//注册Bean的条件,必须有 ZuulProxyMarkerConfiguration.Marker 的实例
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
...省略...
3.Zuul
自动配置ZuulServerAutoConfiguration
ZuulProxyAutoConfiguration
继承了ZuulServerAutoConfiguration
,我们先跟一下它父类
/**
* @author Spencer Gibb
* @author Dave Syer
* @author Biju Kunjummen
*/
@Configuration
//开启配置:ZuulProperties ,这里面是以 zuul 打头的配置
@EnableConfigurationProperties({
ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
//必须满足有 ZuulServerMarkerConfiguration.Marker 的实例才能被注册
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
// FIXME @Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {
//绑定zuul的配置信息,这里面是以 zuul 打头的配置
@Autowired
protected ZuulProperties zuulProperties;
//以server打头的服务器配置
@Autowired
protected ServerProperties server;
//注入请求错误的控制器,主要返回错误页面的路径
@Autowired(required = false)
private ErrorController errorController;
@Bean
public HasFeatures zuulFeature() {
return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
}
//RouteLocator that composes multiple RouteLocators. :
//多路由组合定位器
@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
Collection<RouteLocator> routeLocators) {
return new CompositeRouteLocator(routeLocators);
}
//简单的路由定位器
@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
return new SimpleRouteLocator(this.server.getServlet().getServletPrefix(),
this.zuulProperties);
}
//Zuul的请求入口控制器
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
// MVC HandlerMapping that maps incoming request paths to remote services.
//他是做请求路径和远程服务的映射的,是 HandlerMapping的实现
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
//定义ZuulRefreshListener zuul刷新的监听器
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
// Core Zuul servlet which intializes and orchestrates zuulFilter execution
//这里在注册ZuulServlet 这样的一个servlet, 这个东西了不得了,
//他是负责核心ZuulServlet初始化和调用zuulFilter执行,跟DispatcherServlet差不多
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(new ZuulServlet(),
this.zuulProperties.getServletPattern());
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}
// pre filters :
//在pre过滤器中,它是第一个执行的过滤器,检测请求是用 DispatcherServlet还是 ZuulServlet,将结果设置到RequestContext中
@Bean
public ServletDetectionFilter servletDetectionFilter() {
return new ServletDetectionFilter();
}
//前置过滤器,用于解析表单数据并将其重新编码,只是针对于表单数据,即:content-type中必须带有“application/x-www-form-urlencoded”或“multipart/form-data”
@Bean
public FormBodyWrapperFilter formBodyWrapperFilter() {
return new FormBodyWrapperFilter();
}
//设置请求过程是否开启debug,将当前请求上下文中的debugRouting和debugRequest参数设置为true
@Bean
public DebugFilter debugFilter() {
return new DebugFilter();
}
//主要是将原始请求进行包装,将原始的HttpServletRequest请求包装成Servlet30RequestWrapper类型
@Bean
public Servlet30WrapperFilter servlet30WrapperFilter() {
return new Servlet30WrapperFilter();
}
// post filters
//下面是定义一系列的后置过滤器,SendResponseFilter是Zuul的最后一个Filter,负责最终响应结果的输出
@Bean
public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
return new SendResponseFilter(zuulProperties);
}
//这个是用来发送错误的Filter
@Bean
public SendErrorFilter sendErrorFilter() {
return new SendErrorFilter();
}
//它使用RequestDispatcher转发请求
@Bean
public SendForwardFilter sendForwardFilter() {
return new SendForwardFilter();
}
整理一下ZuulServerAutoConfiguration
配置类里面做的事情
- 1.注册了多路由组合定位器
CompositeRouteLocator
,它是由多个RouteLocator
组成的 ,路由定位器就是根据请求的path来定位对应的微服务的。 2.注册了简单的路由定位器
SimpleRouteLocator
3.注册了很多内置的FiIlter,包括前置FiIlter,后置FiIlter,路由FiIlter,异常FiIlter
4.注册了
ZuulHandlerMapping
是对path和远程服务的映射5.注册了zuulServlet : 请求的分发器类似于DispatcherServlet
6.注册了
ZuulController
,ZuulServlet
会通过调用其父类ServletWrappingController
的service再实现对请求的调用4.Zuul自动配置
ZuulProxyAutoConfiguration
我们看了父类
ZuulServerAutoConfiguration
大概知道了里面配置的内容,回到ZuulProxyAutoConfiguration
看一下它做了哪些配置/** * @author Spencer Gibb * @author Dave Syer * @author Biju Kunjummen */ @Configuration @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class, RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class, RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class, HttpClientConfiguration.class }) //配置激活条件,必须有ZuulProxyMarkerConfiguration.Marker的实例 @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class) public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration { ...省略代码... //服务发现的路由定位器,它将静态的配置RouteLocator路由与DiscoveryClient路由组合在一起, 通过服务发现的方式进行路由 @Bean @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class) public DiscoveryClientRouteLocator discoveryRouteLocator() { return new DiscoveryClientRouteLocator(this.server.getServlet().getServletPrefix(), this.discovery, this.zuulProperties, this.serviceRouteMapper, this.registration); } // pre filters : 前置过滤 //Pre ZuulFilter可以根据提供的RouteLocator确定在哪里以及如何进行路由。 还为下游请求设置各种与代理相关的标头。 @Bean public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) { return new PreDecorationFilter(routeLocator, this.server.getServlet().getServletPrefix(), this.zuulProperties, proxyRequestHelper); } // route filters :路由过滤,实现了Ribbon的负载均衡和hystrix @Bean public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper, RibbonCommandFactory<?> ribbonCommandFactory) { RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers); return filter; } //简单主机的路由Filter @Bean @ConditionalOnMissingBean({ SimpleHostRoutingFilter.class, CloseableHttpClient.class}) public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties, ApacheHttpClientConnectionManagerFactory connectionManagerFactory, ApacheHttpClientFactory httpClientFactory) { return new SimpleHostRoutingFilter(helper, zuulProperties, connectionManagerFactory, httpClientFactory); } @Bean @ConditionalOnMissingBean({ SimpleHostRoutingFilter.class}) public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper, ZuulProperties zuulProperties, CloseableHttpClient httpClient) { return new SimpleHostRoutingFilter(helper, zuulProperties, httpClient); } //服务名和路由组件的映射器,提供一种在路由和发现的服务名称之间应用约定的方法 @Bean @ConditionalOnMissingBean(ServiceRouteMapper.class) public ServiceRouteMapper serviceRouteMapper() { return new SimpleServiceRouteMapper(); }
在 ZuulProxyAutoConfiguration 中做的事情比较简单,就是定义了一堆Filter,包括:
- DiscoveryClientRouteLocator : 静态路由定位器,通过服务发现的方式找到要调用的服务
- PreDecorationFilter :Pre Filter可以根据提供的RouteLocator确定在哪里以及如何进行路由。 还为下游请求设置各种与代理相关的标头。
- RibbonRoutingFilter :route Filter:路由过滤,实现了Ribbon的负载均衡和hystrix
- SimpleHostRoutingFilter : 简单主机的路由Filter,它通过HttpClient将请求发送到预定的URL, URL可在RequestContext.getRouteHost()中找到。
二.Zuul的核心组件
1.ZuulController
请求入口控制器
ZuulController
还是挺重要的,它是zuul的请求入口控制器,它创建了ZuulServlet
去执行请求,ZuulController
的源码:
public class ZuulController extends ServletWrappingController {
public ZuulController() {
//把 ZuulServlet 设置给父类
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
// We don't care about the other features of the base class, just want to
// handle the request
//执行父类的请求方法
return super.handleRequestInternal(request, response);
}
finally {
// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
RequestContext.getCurrentContext().unset();
}
}
}
父类 ServletWrappingController
源码:
public class ServletWrappingController extends AbstractController implements BeanNameAware, InitializingBean, DisposableBean {
...省略...
@Nullable
private Servlet servletInstance;
//反射创建 ZuulServlet和初始化
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
throw new IllegalArgumentException("'servletClass' is required");
} else {
if (this.servletName == null) {
this.servletName = this.beanName;
}
//反射创建 ZuulServlet和初始化
this.servletInstance = (Servlet)ReflectionUtils.accessibleConstructor(this.servletClass, new Class[0]).newInstance();
this.servletInstance.init(new ServletWrappingController.DelegatingServletConfig());
}
}
//核心方法,调用 servletInstance 执行请求
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Assert.state(this.servletInstance != null, "No Servlet instance");
//调用 ZuulServlet执行请求
this.servletInstance.service(request, response);
return null;
}
2.RouteLocator
路由定位器
RouteLocator是路由定位器,就是根据请求的path来定位对应的微服务的 ,在ZuulServerAutoConfiguration
中定义了 CompositeRouteLocator
多路由组合定位器 和 SimpleRouteLocator
简单路由定位器,在ZuulProxyAutoConfiguration
中定义了 DiscoveryClientRouteLocator ,我们看一下 继承体系RouteLocator
是路由定位器接口,提供了
- getIgnoredPaths:获取忽略的服务paths集合,
- getRoutes :获取服务路由Route集合 ,这里的Route是对yml中 zuul.routes的封装
getMatchingRoute :根据path获取路由Route
```java
public interface RouteLocator {/**
Ignored route paths (or patterns), if any.
*/
Collection getIgnoredPaths();/**
A map of route path (pattern) to location (e.g. service id or URL).
*/
List getRoutes();/**
- Maps a path to an actual route with full metadata.
*/
Route getMatchingRoute(String path);
}
`CompositeRouteLocator` 是多路由组合定位器,它主要是调用多个路由定位器根据path找到Route
```java
public class CompositeRouteLocator implements RefreshableRouteLocator {
//省略
@Override
public Route getMatchingRoute(String path) {
//遍历所有的 routeLocators
for (RouteLocator locator : routeLocators) {
//通过 path找到 Route
Route route = locator.getMatchingRoute(path);
if (route != null) {
return route;
}
}
return null;
}
}
SimpleRouteLocator
是最简单的路由定位器,根据path找到route返回
protected Route getSimpleMatchingRoute(final String path) {
if (log.isDebugEnabled()) {
log.debug("Finding route for path: " + path);
}
//获取所有的路由
// This is called for the initialization done in getRoutesMap()
getRoutesMap();
if (log.isDebugEnabled()) {
log.debug("servletPath=" + this.dispatcherServletPath);
log.debug("zuulServletPath=" + this.zuulServletPath);
log.debug("RequestUtils.isDispatcherServletRequest()="
+ RequestUtils.isDispatcherServletRequest());
log.debug("RequestUtils.isZuulServletRequest()="
+ RequestUtils.isZuulServletRequest());
}
//调整路径
String adjustedPath = adjustPath(path);
//根据Path查找Route
ZuulRoute route = getZuulRoute(adjustedPath);
return getRoute(route, adjustedPath);
}
//根据path匹配Route
protected ZuulRoute getZuulRoute(String adjustedPath) {
//不忽略
if (!matchesIgnoredPatterns(adjustedPath)) {
//遍历所有的route
for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
String pattern = entry.getKey();
log.debug("Matching pattern:" + pattern);
//根据path简单匹配 route,返回route
if (this.pathMatcher.match(pattern, adjustedPath)) {
return entry.getValue();
}
}
}
return null;
}
重点看一下 DiscoveryClientRouteLocator
,配置RouteLocator
路由与DiscoveryClient
路由组合在一起,优先使用服务发现方式,源码
/**
* A {@link RouteLocator} that combines static, configured routes with those from a
* {@link DiscoveryClient}. The discovery client takes precedence.
*
* @author Spencer Gibb
* @author Dave Syer
*/
public class DiscoveryClientRouteLocator extends SimpleRouteLocator
implements RefreshableRouteLocator {
private static final Log log = LogFactory.getLog(DiscoveryClientRouteLocator.class);
public static final String DEFAULT_ROUTE = "/**";
//服务发现客户端,用来从注册中心找服务的
private DiscoveryClient discovery;
//zuul的配置,以zuul开头的配置项
private ZuulProperties properties;
//提供一种在 路由 和 发现的服务名称 之间应用约定的方法
private ServiceRouteMapper serviceRouteMapper;
//初始化
public DiscoveryClientRouteLocator(String servletPath, DiscoveryClient discovery,
ZuulProperties properties, ServiceInstance localServiceInstance) {
super(servletPath, properties);
//添加忽略的服务,对应配置:ignored-services: "*"
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;
}
//定位路由,读取yml配置的zuul.routes中的路由,然后把需要忽略的忽略掉,加上前缀返回。
@Override
protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
//拿到所有的routes配置添加到map中,对应yml中的zuul.routes配置
routesMap.putAll(super.locateRoutes());
if (this.discovery != null) {
Map<String, ZuulRoute> staticServices = new LinkedHashMap<>();
for (ZuulRoute route : routesMap.values()) {
//取到配置的每个路由的微服务的ID
String serviceId = route.getServiceId();
if (serviceId == null) {
serviceId = route.getId();
}
if (serviceId != null) {
//以 服务ID为key把每个路由配置放到staticServices中
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);
}
}
//通过工具判断哪些服务要忽略,不忽略的加入routesMap中
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;
}
3.Zuul的内置Filter详解
ServletDetectionFilter
前置通知 ,执行顺序 -3 , 该Filter是用来标记该请求是通过 DispatcherServlet处理还是通过 ZuulServlet处理run()把判断结果以boolean值的方式保存到HttpServletRequest中,后续的处理中就可以通过它获取到这个标记做不同的处理,而这个filter执行的顺序是 -3(filterOrder() 方法) ,越小越先执行
/**
* Detects whether a request is ran through the {@link DispatcherServlet} or {@link ZuulServlet}.
* The purpose was to detect this up-front at the very beginning of Zuul filter processing
* and rely on this information in all filters.
* RequestContext is used such that the information is accessible to classes
* which do not have a request reference.
* @author Adrian Ivan
*/
public class ServletDetectionFilter extends ZuulFilter {
public ServletDetectionFilter() {
}
//前置通知
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* Must run before other filters that rely on the difference between
* DispatcherServlet and ZuulServlet.
*/
//filterOrder 决定了这个过滤器的执行顺序 这里是 :-3 见
//public static final int SERVLET_DETECTION_FILTER_ORDER = -3;
@Override
public int filterOrder() {
return SERVLET_DETECTION_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
//判断结果保存到 HttpServletRequest中
HttpServletRequest request = ctx.getRequest();
//如果请求中设置了DispatcherServletRequest属性就给RequestContext 设置 IS_DISPATCHER_SERVLET_REQUEST_KEY = true
if (!(request instanceof HttpServletRequestWrapper)
&& isDispatcherServletRequest(request)) {
//上下文设置:isDispatcherServletRequest = true
ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);
} else {
//上下文设置:isDispatcherServletRequest = false
ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);
}
return null;
}
//判断当前请求是否是DispatcherServletRequest
private boolean isDispatcherServletRequest(HttpServletRequest request) {
return request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;
}
FormBodyWrapperFilter
前置通知,执行顺序 -1 , 解析表单数据并为后续处理重新编码,由于后续的请求中,将符合要求的请求体包装成FormBodyRequestWrapper对象。
/**
* Pre {@link ZuulFilter} that parses form data and reencodes it for downstream services
*
* @author Dave Syer
*/
public class FormBodyWrapperFilter extends ZuulFilter {
...省略...
@Override
public String filterType() {
//前置Filter
return PRE_TYPE;
}
@Override
public int filterOrder() {
//执行顺序 -1
return FORM_BODY_WRAPPER_FILTER_ORDER;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
//处理请求
HttpServletRequest request = ctx.getRequest();
FormBodyRequestWrapper wrapper = null;
if (request instanceof HttpServletRequestWrapper) {
HttpServletRequest wrapped = (HttpServletRequest) ReflectionUtils
.getField(this.requestField, request);
//包装成 FormBodyRequestWrapper
wrapper = new FormBodyRequestWrapper(wrapped);
ReflectionUtils.setField(this.requestField, request, wrapper);
if (request instanceof ServletRequestWrapper) {
ReflectionUtils.setField(this.servletRequestField, request, wrapper);
}
}
else {
//包装成 FormBodyRequestWrapper
wrapper = new FormBodyRequestWrapper(request);
ctx.setRequest(wrapper);
}
if (wrapper != null) {
ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
}
return null;
}
...省略...
DebugFilter
开启调试标记,如果请求中设置了“debug”请求参数, RequestContext调试属性设置为true。说白了就是通过 reques中的debug参数来激活调试信息,这样当线上环境出现问题的时候,可以通过请求参数的方式来激活这些debug信息以帮助分析问题,前置通知 ,执行顺序 1
/**
* Pre {@link ZuulFilter} that sets {@link RequestContext} debug attributes to true if
* the "debug" request parameter is set.
*
* @author Spencer Gibb
*/
public class DebugFilter extends ZuulFilter {
private static final DynamicBooleanProperty ROUTING_DEBUG = DynamicPropertyFactory
.getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, false);
private static final DynamicStringProperty DEBUG_PARAMETER = DynamicPropertyFactory
.getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "debug");
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return DEBUG_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {
return true;
}
return ROUTING_DEBUG.get();
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
//设置上下文,开启Routing和Request的debug功能
ctx.setDebugRouting(true);
ctx.setDebugRequest(true);
return null;
}
Servlet30WrapperFilter
这里是对原始的HttpServletRequest请求包装成Servlet30RequestWrapper对象即要兼容3.0。zuul默认只是兼容2.5,前置通知 ,执行顺序 -2
/**
* Pre {@link ZuulFilter} that wraps requests in a Servlet 3.0 compliant wrapper.
* Zuul's default wrapper is only Servlet 2.5 compliant.
* @author Spencer Gibb
*/
public class Servlet30WrapperFilter extends ZuulFilter {
private Field requestField = null;
public Servlet30WrapperFilter() {
this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class,
"req", HttpServletRequest.class);
Assert.notNull(this.requestField,
"HttpServletRequestWrapper.req field not found");
this.requestField.setAccessible(true);
}
protected Field getRequestField() {
return this.requestField;
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return SERVLET_30_WRAPPER_FILTER_ORDER;
}
@Override
public Object run() {
//把请求包装成 Servlet30RequestWrapper
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request instanceof HttpServletRequestWrapper) {
request = (HttpServletRequest) ReflectionUtils.getField(this.requestField,
request);
ctx.setRequest(new Servlet30RequestWrapper(request));
}
else if (RequestUtils.isDispatcherServletRequest()) {
// If it's going through the dispatcher we need to buffer the body
ctx.setRequest(new Servlet30RequestWrapper(request));
}
return null;
}
你现在知道为什么他叫 Servlet30WrapperFilter 了吗?
SendResponseFilter
后置通知 ,处理请求响应,执行顺序 1000
/**
* Post {@link ZuulFilter} that writes responses from proxied requests to the current response.
*
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
*/
public class SendResponseFilter extends ZuulFilter {
...省略...
@Override
public Object run() {
try {
//添加响应头
addResponseHeaders();
//添加响应的内容
writeResponse();
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
private void writeResponse() throws Exception {
RequestContext context = RequestContext.getCurrentContext();
// there is no body to send
if (context.getResponseBody() == null
&& context.getResponseDataStream() == null) {
return;
}
HttpServletResponse servletResponse = context.getResponse();
if (servletResponse.getCharacterEncoding() == null) {
// only set if not set
servletResponse.setCharacterEncoding("UTF-8");
}
OutputStream outStream = servletResponse.getOutputStream();
InputStream is = null;
try {
if (RequestContext.getCurrentContext().getResponseBody() != null) {
String body = RequestContext.getCurrentContext().getResponseBody();
writeResponse(
new ByteArrayInputStream(
body.getBytes(servletResponse.getCharacterEncoding())),
outStream);
return;
}
...省略...
//写响应结果
private void writeResponse(InputStream zin, OutputStream out) throws Exception {
byte[] bytes = buffers.get();
int bytesRead = -1;
while ((bytesRead = zin.read(bytes)) != -1) {
out.write(bytes, 0, bytesRead);
}
}
翻译大致意思为把代理请求的响应写入到当前响应,String body = RequestContext.getCurrentContext().getResponseBody();
获取到响应内容 ,通过 servletResponse.getOutputStream();
写出去 ,
我们从源码中可以看到该过滤器会检查请求上下文中是否包含请求响应相关的头信息、响应数据流或是响应体,然后利用请求上下文的响应信息来组织需要发送回客户端的响应内容。
SendErrorFilter
错误处理过滤器 ,把错误重定向到/error路径上,执行顺序 0
/**
* Error {@link ZuulFilter} that forwards to /error (by default) if {@link RequestContext#getThrowable()} is not null.
*
* @author Spencer Gibb
*/
//TODO: move to error package in Edgware
public class SendErrorFilter extends ZuulFilter {
private static final Log log = LogFactory.getLog(SendErrorFilter.class);
protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";
//异常重定向路径
@Value("${error.path:/error}")
private String errorPath;
@Override
public String filterType() {
return ERROR_TYPE;
}
@Override
public int filterOrder() {
return SEND_ERROR_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
// only forward to errorPath if it hasn't been forwarded to already
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();
//处理异常错误码等
request.setAttribute("javax.servlet.error.status_code", exception.nStatusCode);
log.warn("Error during filtering", exception);
request.setAttribute("javax.servlet.error.exception", exception);
if (StringUtils.hasText(exception.errorCause)) {
request.setAttribute("javax.servlet.error.message", exception.errorCause);
}
RequestDispatcher dispatcher = request.getRequestDispatcher(
this.errorPath);
if (dispatcher != null) {
ctx.set(SEND_ERROR_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
ctx.setResponseStatusCode(exception.nStatusCode);
dispatcher.forward(request, ctx.getResponse());
}
}
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
SendForwardFilter
用来处理路由规则中的forward本地跳转配置 ,执行顺序 5000
/**
* Route {@link ZuulFilter} that forwards requests using the {@link RequestDispatcher}.
* Forwarding location is located in the {@link RequestContext} attribute {@link org.springframework.cloud.netflix.zuul.filters.support.FilterConstants#FORWARD_TO_KEY}.
* Useful for forwarding to endpoints in the current application.
用户RequestDispatcher 进行本地应用端点的Forwarding
* @author Dave Syer
*/
public class SendForwardFilter extends ZuulFilter {
...省略...
@Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
String path = (String) ctx.get(FORWARD_TO_KEY);
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;
}
下面是 ZuulProxyAutoConfiguration
中还定义了一些过滤器
PreDecorationFilter
Pre 前置filter可以根据提供的RouteLocator确定在哪里以及如何进行路由。 还为下游请求设置各种与代理相关的请求头,执行顺序 5
/**
* Pre {@link ZuulFilter} that determines where and how to route based on the supplied {@link RouteLocator}.
* Also sets various proxy related headers for downstream requests.
*/
public class PreDecorationFilter extends ZuulFilter {
...省略...
@Override
public Object run() {
//请求上下文
RequestContext ctx = RequestContext.getCurrentContext();
//请求路径
final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
//根据请求地址,匹配匹配路由(url对应要访问的服务)
Route route =
this.routeLocator.getMatchingRoute(requestURI);
if (route != null) {
//从路由中获取请求服务id
String location = route.getLocation();
if (location != null) {
//设置请求上下文相关信息
//请求的资源路径 path
ctx.put(REQUEST_URI_KEY, route.getPath());
//路由的id
ctx.put(PROXY_KEY, route.getId());
if (!route.isCustomSensitiveHeaders()) {
//忽略敏感的请求头
this.proxyRequestHelper
.addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0]));
}
else {
this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0]));
}
if (route.getRetryable() != null) {
//可否重试
ctx.put(RETRYABLE_KEY, route.getRetryable());
}
if (location.startsWith(HTTP_SCHEME+":") || location.startsWith(HTTPS_SCHEME+":")) {
ctx.setRouteHost(getUrl(location));
ctx.addOriginResponseHeader(SERVICE_HEADER, location);
}
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 {
//设置服务id绑定到上下文个,在RibbonReques中使用
// set serviceId for use in filters.route.RibbonRequest
ctx.set(SERVICE_ID_KEY, location);
ctx.setRouteHost(null);
ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
}
//添加代理请求头
if (this.properties.isAddProxyHeaders()) {
addProxyHeaders(ctx, route);
String xforwardedfor = ctx.getRequest().getHeader(X_FORWARDED_FOR_HEADER);
String remoteAddr = ctx.getRequest().getRemoteAddr();
if (xforwardedfor == null) {
xforwardedfor = remoteAddr;
}
else if (!xforwardedfor.contains(remoteAddr)) {
// Prevent duplicates
xforwardedfor += ", " + remoteAddr;
}
ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);
}
if (this.properties.isAddHostHeader()) {
ctx.addZuulRequestHeader(HttpHeaders.HOST, toHostHeader(ctx.getRequest()));
}
...省略...
RibbonRoutingFilter
Routing过滤器,使用Ribbon和Hystrix来向服务实例发起请求 ,有服务熔断机制,执行顺序 10
/**
* Route {@link ZuulFilter} that uses Ribbon, Hystrix and pluggable http clients to send requests.
* ServiceIds are found in the {@link RequestContext} attribute {@link org.springframework.cloud.netflix.zuul.filters.support.FilterConstants#SERVICE_ID_KEY}.
通过 Ribbon 和 Hystrix 向http客户端发送请求
通过 RequestContext找到 ServiceIds服务id ,
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
*/
public class RibbonRoutingFilter extends ZuulFilter {
...省略...
@Override
public Object run() {
//获取请求上下文
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
//创建一个 RibbonCommandContext Ribbon命令上下文,用来发请求
RibbonCommandContext commandContext = buildCommandContext(context);
//发送请求,获取到结果
ClientHttpResponse response = forward(commandContext);
setResponse(response);
return response;
}
catch (ZuulException ex) {
throw new ZuulRuntimeException(ex);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
}
//根据RequestContext 请求上下文,获取请求服务id,url等封装成RibbonCommandContext
protected RibbonCommandContext buildCommandContext(RequestContext context) {
HttpServletRequest request = context.getRequest();
MultiValueMap<String, String> headers = this.helper
.buildZuulRequestHeaders(request);
MultiValueMap<String, String> params = this.helper
.buildZuulRequestQueryParams(request);
String verb = getVerb(request);
InputStream requestEntity = getRequestBody(request);
if (request.getContentLength() < 0 && !verb.equalsIgnoreCase("GET")) {
context.setChunkedRequestBody();
}
String serviceId = (String) context.get(SERVICE_ID_KEY);
Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
Object loadBalancerKey = context.get(LOAD_BALANCER_KEY);
String uri = this.helper.buildZuulRequestURI(request);
// remove double slashes
uri = uri.replace("//", "/");
long contentLength = useServlet31 ? request.getContentLengthLong(): request.getContentLength();
return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
}
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
Map<String, Object> info = this.helper.debug(context.getMethod(),
context.getUri(), context.getHeaders(), context.getParams(),
context.getRequestEntity());
RibbonCommand command = this.ribbonCommandFactory.create(context);
try {
//执行请求
ClientHttpResponse response = command.execute();
this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
return response;
}
catch (HystrixRuntimeException ex) {
//处理异常
return handleException(info, ex);
}
}
//处理异常
protected ClientHttpResponse handleException(Map<String, Object> info,
HystrixRuntimeException ex) throws ZuulException {
int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
Throwable cause = ex;
String message = ex.getFailureType().toString();
ClientException clientException = findClientException(ex);
if (clientException == null) {
clientException = findClientException(ex.getFallbackException());
}
if (clientException != null) {
if (clientException
.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
statusCode = HttpStatus.SERVICE_UNAVAILABLE.value();
}
cause = clientException;
message = clientException.getErrorType().toString();
}
info.put("status", String.valueOf(statusCode));
throw new ZuulException(cause, "Forwarding error", statusCode, message);
}
SimpleHostRoutingFilter
通过RequestContext#getRouteHost()
找到调用的服务地址 ,使用http客户端实现调用 ,他和RibbonRoutingFilter
的区别是没有使用Hystrix所以并没有线程隔离和断路器的保护。
执行顺序 100
/**
* Route {@link ZuulFilter} that sends requests to predetermined URLs via apache
* {@link HttpClient}. URLs are found in {@link RequestContext#getRouteHost()}.
通过RequestContext#getRouteHost()找到调用的服务地址 ,使用apache的http客户端实现调用
*
* @author Spencer Gibb
* @author Dave Syer
* @author Bilal Alp
* @author Gang Li
*/
public class SimpleHostRoutingFilter extends ZuulFilter {
...省略...
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
MultiValueMap<String, String> headers = this.helper
.buildZuulRequestHeaders(request);
MultiValueMap<String, String> params = this.helper
.buildZuulRequestQueryParams(request);
String verb = getVerb(request);
InputStream requestEntity = getRequestBody(request);
if (request.getContentLength() < 0) {
context.setChunkedRequestBody();
}
//获取请求地址
String uri = this.helper.buildZuulRequestURI(request);
this.helper.addIgnoredHeaders();
try {
//发送请求
CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
headers, params, requestEntity);
setResponse(response);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
return null;
}
------------------------------------
//使用 httpclient 发送请求
private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb,
String uri, HttpServletRequest request, MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, InputStream requestEntity)
throws Exception {
Map<String, Object> info = this.helper.debug(verb, uri, headers, params,
requestEntity);
//请求主机
URL host = RequestContext.getCurrentContext().getRouteHost();
HttpHost httpHost = getHttpHost(host);
//请求地址
uri = StringUtils.cleanPath((host.getPath() + uri).replaceAll("/{2,}", "/"));
int contentLength = request.getContentLength();
ContentType contentType = null;
if (request.getContentType() != null) {
contentType = ContentType.parse(request.getContentType());
}
InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength,
contentType);
HttpRequest httpRequest = buildHttpRequest(verb, uri, entity, headers, params,
request);
try {
log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " "
+ httpHost.getSchemeName());
//发送请求
CloseableHttpResponse zuulResponse = forwardRequest(httpclient, httpHost,
httpRequest);
this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(),
revertHeaders(zuulResponse.getAllHeaders()));
return zuulResponse;
}
finally {
// When HttpClient instance is no longer needed,
// shut down the connection manager to ensure
// immediate deallocation of all system resources
// httpclient.getConnectionManager().shutdown();
}
}
------------------------------------
protected HttpRequest buildHttpRequest(String verb, String uri,
InputStreamEntity entity, MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, HttpServletRequest request) {
HttpRequest httpRequest;
String uriWithQueryString = uri + (this.forceOriginalQueryStringEncoding
? getEncodedQueryString(request) :
this.helper.getQueryString(params));
//处理各种请求方式
switch (verb.toUpperCase()) {
case "POST":
HttpPost httpPost = new HttpPost(uriWithQueryString);
httpRequest = httpPost;
httpPost.setEntity(entity);
break;
case "PUT":
HttpPut httpPut = new HttpPut(uriWithQueryString);
httpRequest = httpPut;
httpPut.setEntity(entity);
break;
case "PATCH":
HttpPatch httpPatch = new HttpPatch(uriWithQueryString);
httpRequest = httpPatch;
httpPatch.setEntity(entity);
break;
case "DELETE":
BasicHttpEntityEnclosingRequest entityRequest = new BasicHttpEntityEnclosingRequest(
verb, uriWithQueryString);
httpRequest = entityRequest;
entityRequest.setEntity(entity);
break;
default:
httpRequest = new BasicHttpRequest(verb, uriWithQueryString);
log.debug(uriWithQueryString);
}
httpRequest.setHeaders(convertHeaders(headers));
return httpRequest;
}
------------------------------------
//最终执行请求
private CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient,
HttpHost httpHost, HttpRequest httpRequest) throws IOException {
return httpclient.execute(httpHost, httpRequest);
}
到这里我们把 ZuulProxyAutoConfiguration 自动配置类中定义的比较重要的一些过滤器都介绍完了 ,zuul在执行过程中就会按照这些filter的调用顺序去执行,我们来用表格整理一下
类型 | 过滤器 | 描述 | 顺序 |
---|---|---|---|
pre | ServletDetectionFilter | 在pre过滤器中,ServletDetectionFilter是第一个执行的过滤器,检测请求是用 DispatcherServlet还是 ZuulServlet,将结果设置到RequestContext中 | -3 |
pre | Servlet30WrapperFilter | 主要是将原始请求进行包装,将原始的HttpServletRequest请求包装成Servlet30RequestWrapper类型 | -2 |
pre | FormBodyWrapperFilter | 同Servlet30RequestWrapper一样,也是对请求的一个包装,只不过他只包装表单数据,即:content-type中必须带有“application/x-www-form-urlencoded”或“multipart/form-data” | -1 |
error | SendErrorFilter | 这个是用来发送错误的Filter | 0 |
pre | DebugFilter | 设置请求过程是否开启debug,将当前请求上下文中的debugRouting 和debugRequest 参数设置为true |
1 |
pre | PreDecorationFilter | 基本的路由转发配置,根据uri调用哪一个route过滤器 | 5 |
route | RibbonRoutingFilter | 服务路由的过滤器,使用用Ribbon 做负载均衡,hystrix做熔断 | 10 |
route | SimpleHostRoutingFilter | 简单主机路由过滤器,如果使用url路由,则用这个过滤器 | 100 |
route | SendForwardFilter | 它使用RequestDispatcher转发请求 | 500 |
post | SendResponseFilter | SendResponseFilter是Zuul的最后一个Filter,负责最终响应结果的输出。 | 1000 |
那这一章我们分析到这里 ,下一章我们跟踪一下zuul的执行流程,看他是如果把这些filter串联起来的 。