我们知道在SpringBoot中,第三方库在META-INF/spring.factories
文件中指定自动配置文件。于是我们从spring-cloud-netflix-zuul-2.0.0.RC1.jar
的spring.factories
文件入手:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration
可以看到spring.factories
文件中指定了两个类:ZuulServerAutoConfiguration
、ZuulProxyAutoConfiguration
用于自动配置。我们来看看ZuulServerAutoConfiguration
类中配置了哪些相关的bean,这里我们主要讲解CompositeRouteLocator
和SimpleRouteLocator
@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
Collection<RouteLocator> routeLocators) {
return new CompositeRouteLocator(routeLocators);
}
首先定义了CompositeRouteLocator
,它是核心的路由定位器。路由定位器用于寻找特定路径映射的路由。它有一个统一的接口RouteLocator
:
public interface RouteLocator {
// 获取忽略的路径
Collection<String> getIgnoredPaths();
// 获取路由的列表
List<Route> getRoutes();
// 获取指定路径对应的路由
Route getMatchingRoute(String path);
}
RouteLocator
有三个实现类:SimpleRouteLocator
、DiscoveryClientRouteLocator
、CompositeRouteLocator
。它们的关系图如下:
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
ZuulHandlerMapping
是一个用于MVC处理的HandlerMapping
,它用于根据请求的path映射处理请求的Handler。
@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;
}
这里新建了一个ServletRegistrationBean
——Servlet的注册器。通过这个ServletRegistrationBean
,向servlet容器中注册了一个ZuulServlet。
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
这里注册了一个事件监听器,用于监听事件来刷新路由。
前文我们看到ZuulProxyAutoConfiguration
配置文件中配置了一个事件监听器ZuulRefreshListener
来刷新路由。
当Spring启动完成或者Eureka中的服务发生变化时都会发出事件,ZuulRefreshListener
收到事件之后进行路由的刷新。调用流程如下:
DiscoveryClientRouteLocator.locateRoutes
方法是初始化路由的核心,主要分为两步:
SimpleRouteLocator.locateRoutes
加载配置文件中的路由接着上一篇RouteLocator
我们来看看ZuulServerAutoConfiguration
类中配置了哪些相关的bean。
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
ZuulHandlerMapping
是一个用于MVC处理的HandlerMapping
,它用于根据请求的path映射处理请求的Handler。
ZuulHandlerMapping在注册发生在第一次请求发生的时候,在ZuulHandlerMapping.lookupHandler
方法中执行。调用流程如下:
在ZuulHandlerMapping.registerHandlers
方法中首先获取所有的路由,然后调用AbstractUrlHandlerMapping.registerHandler
将路由中的路径和ZuulHandlerMapping
相关联。
当接收到一个请求后,处理请求的过程统一在DispatcherServlet.doDispatch
中进行。
在DispatcherServlet.doDispatch
方法中调用DispatcherServlet.getHandler
方法获取handler,在该方法中遍历所有的HandlerMapping,调用其getHandler
方法获得HandlerExecutionChain,如果不为null说明正是我们要找的handler。
对于ZuulHandlerMapping的getHandler
方法的调用流程如下:
AbstractUrlHandlerMapping.getHandlerInternal:根据request的path查找匹配的handler
getHandlerInternal
方法根据lookupPath
(请求路径)、request
(请求)调用ZuulHandlerMapping.lookupHandler
方法查找匹配的handler。
ZuulHandlerMapping.lookupHandler
的调用流程如下:
判断是否在请求errorPath
请求的路径是否处于routeLocator被忽略的路径中
请求上下文中是否包含forward.to
调用AbstractUrlHandlerMapping.lookupHandler
AbstractUrlHandlerMapping.lookupHandler
的调用流程如下:
检查handlerMap
中是否包含了请求路径对应的Handler。(handlerMap
是在ZuulHandlerMapping执行registerHandlers()
方法是注册的。将所有Route的路径映射为ZuulController)
将请求路径与handlerMap
中的路径进行匹配,将handlerMap
中匹配的路径添加到matchingPatterns
列表中
从matchingPatterns
列表中取得第一个路径作为最佳匹配的路径bestMatch
从handlerMap
中获取bestMatch对应的Handler,即ZuulController
将handler、bestMatch等包装成HandlerExecutionChain返回
因为返回的handler不为null,调用getHandlerExecutionChain
将其包装成HandlerExecutionChain,加入拦截器信息。返回executionChain
。
ZuulHandlerMapping的调用发生在DispatcherServlet.doDispatch
执行时。调用流程如下:
可以看到ZuulHandlerMapping最终调用了ZuulServlet.service
方法。
Zuul的主要流程发生在ZuulServlet中,它的调用流程如下:
在ZuulServlet.service
方法中,调用各个过滤器对请求进行处理,再将结果设置到response中返回:
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try { init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); // Marks this request as having passed through the "Zuul engine", as opposed to servlets // explicitly bound in web.xml, for which requests will not have the same data attached RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try { preRoute(); } catch (ZuulException e) { error(e); postRoute(); return; } try { route(); } catch (ZuulException e) { error(e); postRoute(); return; } try { postRoute(); } catch (ZuulException e) { error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); }}
ZuulServlet
中的方法都是对ZuulRunner
中方法的包装,调用的是ZuulRunner.init
方法:它将HttpServletRequest
和HttpServletResponse
分拨包装成HttpServletRequestWrapper
和HttpServletResponseWrapper
。然后将他们保存在RequestContext
中,RequestContext
保存在ThreadLocal中,每个请求线程都有不同的RequestContext
。RequestContext
中加入zuulEngineRan=true
的键值对,表示这个请求经过Zuul的处理。preRoute()
、route()
、postRoute()
方法,对请求执行”pre”、”route”、”post”三种过滤器前文我们知道了过滤器的调用在ZuulServlet.service
方法中完成。过滤器是Zuul实现API网关功能最为核心的部件,每一个进入Zuul的HTTP请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。Zuul中的过滤器统一实现了ZuulFilter
抽象类,其中有四个抽象方法:
String filterType();int filterOrder();boolean shouldFilter();Object run();
它们各自的含义和功能总结如下:
pre
、routing
、post
、error
。过滤器的遍历执行在FilterProcessor.runFilters
方法中:
public Object runFilters(String sType) throws Throwable { if (RequestContext.getCurrentContext().debugRouting()) { Debug.addRoutingDebug("Invoking {" + sType + "} type filters"); } boolean bResult = false; List list = FilterLoader.getInstance().getFiltersByType(sType); if (list != null) { for (int i = 0; i < list.size(); i++) { ZuulFilter zuulFilter = list.get(i); Object result = processZuulFilter(zuulFilter); if (result != null && result instanceof Boolean) { bResult |= ((Boolean) result); } } } return bResult;}
可以看到执行流程就是简单的两步:
调用FilterLoader
的getFiltersByType
方法获取响应类型的过滤器
遍历这个类型下所有的过滤器,调用FilterProcessor.processZuulFilter
方法执行过滤器方法。
processZuulFilter
方法主要调用的是ZuulFilter.runFilter
方法,主要流程为两步:
shouldFilter()
方法判断该过滤器是否该执行shouldFilter()
方法返回true
,则调用run()
方法执行过滤器中具体的方法RequestContext
的isDispatcherServletRequest
参数中,这样在后续的过滤器中,我们就可以通过RequestUtils.isDispatcherServletRequest()
和RequestUtils.isZuulServletRequest()
方法判断它以实现不同的处理。一般情况下,发送到API网关的外部请求都会被Spring的DispatcherServlet
处理,除了通过/zuul/
路径访问的请求会绕过DispatcherServlet
,被ZuulServlet
处理,主要用来应对处理大文件上传的情况。另外,对于ZuulServlet
的访问路径/zuul/
,我们可以通过zuul.servletPath
参数来进行修改。HttpServletRequest
包装成Servlet30RequestWrapper
对象application/x-www-form-urlencoded
或multipart/form-data
FormBodyRequestWrapper
对象执行顺序:1
执行条件:请求中的debug
参数(该参数可以通过zuul.debug.parameter
来自定义)为true
,或者配置参数zuul.debug.request
为true
功能:将当前RequestContext
中的debugRouting
和debugRequest
参数设置为true
。
由于在同一个请求的不同声明周期中,都可以访问到这两个值,所以我们在后续的各个过滤器中可以利用这两个值来定义一下debug信息,这样当线上环境出现问题的时候,可以通过请求参数的方式来激活这些debug信息以帮助分析问题。
forward.to
和serviceId
两个参数。如果有一个存在的话,说明当前请求已经被处理过了,因为这两个信息就是根据当前请求的路由信息加载进来的。处理流程如下:
根据request获取请求路径
RequestContext ctx = RequestContext.getCurrentContext();final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
根据请求路径获取路由
Route route = this.routeLocator.getMatchingRoute(requestURI);
在ZuulProxyAutoConfiguration
配置类中我们知道routeLocator是CompositeRouteLocator
,它的getMatchingRoute
方法如下:
public Route getMatchingRoute(String path) { for (RouteLocator locator : routeLocators) { Route route = locator.getMatchingRoute(path); if (route != null) { return route; } } return null;}
可以看到它遍历所有的路由定位器,返回匹配路径的路由定位器。默认情况下,routeLocators
中只有一个DiscoveryClientRouteLocator
。实际上这里调用的就是DiscoveryClientRouteLocator.getMatchingRoute
方法,因为DiscoveryClientRouteLocator
继承了SimpleRouteLocator
,getMatchingRoute
方法实际上位于SimpleRouteLocator
类中。
SimpleRouteLocator.getMatchingRoute
方法调用getSimpleMatchingRoute
,这个方法根据请求路径获取相应的Route,主流程代码如下:
getRoutesMap();String adjustedPath = adjustPath(path);ZuulRoute route = getZuulRoute(adjustedPath);return getRoute(route, adjustedPath);
getRoutesMap
。如果路径与路由的映射关系没有初始化,则在getRoutesMap
方法中进行初始化getZuulRoute
方法,遍历所有路由对应的路径,根据请求路径调用AntPathMatcher.match()
方法找到匹配的路由。getRoute
方法,将传入的ZuulRoute
和path
包装成Route
根据是否能根据请求路径找到路由,执行不同的路径
requestURI
设置为路由的pathproxy
设置为路由的idignoredHeaders
中。否则添加默认的sensitiveHeaders,包括Cookie
、Set-Cookie
、Authorization
。retryable
不为空,则将其添加到RequestContext的retryable
中http
或https
开头,将其添加到RequestContext的routeHost
中,在RequestContext的originResponseHeaders
中添加X-Zuul-Service
与location的键值对;forward:
开头,则将其添加到RequestContext的forward.to
中,将RequestContext的routeHost
设置为null并返回;serviceId
中,将RequestContext的routeHost
设置为null,在RequestContext的originResponseHeaders
中添加X-Zuul-ServiceId
与location的键值对。zuul.addProxyHeaders
参数设置为false
,则在RequestContext的zuulRequestHeaders
中添加一系列请求头:X-Forwarded-Host
、X-Forwarded-Port
、X-Forwarded-Proto
、X-Forwarded-Prefix
、X-Forwarded-For
zuul.addHostHeader
参数设置为false
,则在则在RequestContext的zuulRequestHeaders
中添加host
在RequestContext中将forward.to
设置为forwardURI
,默认情况下forwardURI
为请求路径。
routeHost
为null,serviceId
不为null。即只对通过serviceId配置路由规则的请求生效routeHost
不为null。即只对通过url配置路由规则的请求生效routeHost
参数的物理地址发起请求,该请求是直接通过httpclient包实现的,而没有使用Hystrix命令进行包装,所以这类请求并没有线程隔离和熔断器的保护。forward.to
不为null。即用来处理路由规则中的forward本地跳转配置forward.to
中保存的跳转地址,跳转过去throwable
不为null,且sendErrorFilter.ran
属性为false
。javax.servlet.error.status_code
、javax.servlet.error.exception
、javax.servlet.error.message
三个属性。将RequestContext中的sendErrorFilter.ran
属性设置为true
。然后组织成一个forward到API网关/error
错误端点的请求来产生错误响应。RequestContext中的sendErrorFilter.ran
属性是为了防止error过滤器处理完之后调用postRoute()
再一次发生异常,第二次发生的异常就不再处理。
throwable
属性为null(如果不为null说明已经被error过滤器处理过了,这里的post过滤器就不需要处理了),并且RequestContext中zuulResponseHeaders
、responseDataStream
、responseBody
三者有一样不为null(说明实际请求的响应不为空)。X-Zuul-Debug-Header
、Date
、Content-Type
、Content-Length
等):addResponseHeaders
;发送响应内容:writeResponse
。