zuul源码深度解析(一)- enableZuulServer的初始化
前言
基于spring cloud分析,对应pom文件如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<!--zuul-core及spring-cloud-netflix-core建议从github下载源码后导入项目引入(修改源码常用操作),如果只做学习使用且习惯借助于idle解析jar包,则可以删掉exclusions-->
<exclusions>
<exclusion>
<groupId>com.netflix.zuul</groupId>
<artifactId>zuul-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
</exclusion>
</exclusions>
</dependency>
源码版本:spring-cloud-start-zuul版本1.4.4.RELEASE,zuul-core版本1.3.0。建议对网关的原理及基础知识有一定了解后,再对源码进行解读。
1、zuul的初始化
使用zuul的方式非常简单,在启动类上增加@EnableZuulProxy或EnableZuulServer注解即可。
1.1、@EnableZuulServer的初始化
1.1.1、EnableZuulServer源码分析
主要引入zuulServerMakerConfiguration类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ZuulServerMarkerConfiguration.class)
public @interface EnableZuulServer {
}
1.1.2、ZuulServerMarkerConfiguration源码分析
初始化了Marker对象,由注释可以得知@link ZuulServerAutoConfiguration
/**
* Responsible for adding in a marker bean to trigger activation of
* {@link ZuulServerAutoConfiguration}
*
* @author Biju Kunjummen
*/
@Configuration
public class ZuulServerMarkerConfiguration {
@Bean
public Marker zuulServerMarkerBean() {
return new Marker();
}
class Marker {
}
}
1.1.3、ZuulServerAutoConfiguration源码分析
ZuulServerAutoConfiguration是@EnableZuulServer的核心初始化类,我们做重点分析,类的注解重点关注@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)。由此可知通过Maker对象注入spring容器,所以在application启动类加注解@EnableZuulServer,则spring容器最终会加载@zuulServerAutoConfiguration.该类加载的对象比较多,我们逐一介绍
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {
- zuulProperties:加载application.properties中的配置项,常见的配置如:zuul.routes..path,zuul.routes..url等
@Autowired
protected ZuulProperties zuulProperties;
- routeLocate相关的类,网关对应路由信息的实现类,主要负责加载路由规则,是网关路由转发寻址的核心,SimpleRouteLocator加载的是配置文件的信息,注意注解标注@ConditionalOnMissingBean;CompositeRouteLocator 是组合路由,注解标注为@Primary。RouteLocator 路由规则确认会在后续章节RouteLocator路由规则加载详细介绍。
@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);
}
- ZuulController 继承ServletWrappingController,ZuulHandlerMapping 继承AbstractUrlHandlerMapping都是servlet相关组件,当发送http、https请求是spring mvc做请求分发是会查找handler(zuulHandlerMapping),在通过handlerAdapter找到合适的Handler处理器zuulController,dispatch流程会在后续Zuul请求流程中分析。
@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;
}
- pre filter相关类ServletDetectionFilter ,FormBodyWrapperFilter ,DebugFilter ,Servlet30WrapperFilter
@Bean
public ServletDetectionFilter servletDetectionFilter() {
return new ServletDetectionFilter();
}
@Bean
public FormBodyWrapperFilter formBodyWrapperFilter() {
return new FormBodyWrapperFilter();
}
@Bean
public DebugFilter debugFilter() {
return new DebugFilter();
}
@Bean
public Servlet30WrapperFilter servlet30WrapperFilter() {
return new Servlet30WrapperFilter();
}
过滤器 | 类型 | order | 执行条件 | 描述 |
---|---|---|---|---|
ServletDetectionFilter | pre | -3 | 检测请求是用DispatcherServlet还是zuulServlet,该过滤器是默认过滤器中最先执行的,且总会执行(shouldFilter返回true),严格来说,若请求path,在网关有路由配置,则一定会执行。主要用来检测当前请求是通过DispatcherServlet处理运行,还是zuulServlet来处理运行。检测结果会以布尔类型保存在上下文的isDispatchServletRequest参数 |
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (!(request instanceof HttpServletRequestWrapper)
&& isDispatcherServletRequest(request)) {
ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);
} else {
ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);
}
return null;
}
zuulServlet跟DispatcherServlet的区别在于/zuul(可通过zuul.servletPath配置) 会通过zuulServlet分发,原因在于Content-Type为multipart/form的请求且是DispatcherServlet分发的请求会通过FromBodyWrapperFilter处理封装加强,而对于大文件流的请求,会影响性能。DispacherServlet分发及FromBodyWrapperFilter处理大文件的性能损耗会比较大。所以此类请求可以通过/zuul通过zuulServlet分发。
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean 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;
}
public String getServletPattern() {
String path = this.servletPath;
if (!path.startsWith("/")) {
path = "/" + path;
}
if (!path.contains("*")) {
path = path.endsWith("/") ? (path + "*") : (path + "/*");
}
return path;
}
过滤器 | 类型 | order | 执行条件 | 描述 |
---|---|---|---|---|
Servlet30WrapperFilter | pre | -2 | 总是执行 | 装饰器模式,将原始的HttpServletRequest包装成Servlet30Request对象,实际等同于HttpServletRequest对象,未在代码处看到相关增强的功能 |
@Override
public boolean shouldFilter() {
return true; // TODO: only if in servlet 3.0 env
}
@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));
}
else if (RequestUtils.isDispatcherServletRequest()) {
// If it's going through the dispatcher we need to buffer the body
ctx.setRequest(new Servlet30RequestWrapper(request));
}
return null;
}
过滤器 | 类型 | order | 执行条件 | 描述 |
---|---|---|---|---|
FromBodyWrapperFilter | pre | -1 | Content-Type为application/x-www-from-urlencode或(multipart/form-data且为DispatcherServlet) | 该过滤器主要作用是将符合要求的请求包装成FromBodyRequestWrapper(继承Servlet30WrapperFilter)对象 |
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String contentType = request.getContentType();
// Don't use this filter on GET method
if (contentType == null) {
return false;
}
// Only use this filter for form data and only for multipart data in a
// DispatcherServlet handler
try {
MediaType mediaType = MediaType.valueOf(contentType);
return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)
|| (isDispatcherServletRequest(request)
&& MediaType.MULTIPART_FORM_DATA.includes(mediaType));
}
catch (InvalidMediaTypeException ex) {
return false;
}
}
private boolean isDispatcherServletRequest(HttpServletRequest request) {
return request.getAttribute(
DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;
}
@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);
wrapper = new FormBodyRequestWrapper(wrapped);
ReflectionUtils.setField(this.requestField, request, wrapper);
if (request instanceof ServletRequestWrapper) {
ReflectionUtils.setField(this.servletRequestField, request, wrapper);
}
}
else {
wrapper = new FormBodyRequestWrapper(request);
ctx.setRequest(wrapper);
}
if (wrapper != null) {
ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
}
return null;
}
FromBodyRequestWrapper的相关增强功能如下,可以看到在
getContentType,getContentLength,getContentLengthLong做了相关封装。
private class FormBodyRequestWrapper extends Servlet30RequestWrapper {
private HttpServletRequest request;
private volatile byte[] contentData;
private MediaType contentType;
private int contentLength;
public FormBodyRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public String getContentType() {
if (this.contentData == null) {
buildContentData();
}
return this.contentType.toString();
}
@Override
public int getContentLength() {
if (super.getContentLength() <= 0) {
return super.getContentLength();
}
if (this.contentData == null) {
buildContentData();
}
return this.contentLength;
}
public long getContentLengthLong() {
return getContentLength();
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (this.contentData == null) {
buildContentData();
}
return new ServletInputStreamWrapper(this.contentData);
}
private synchronized void buildContentData() {
if (this.contentData != null) {
return;
}
try {
MultiValueMap<String, Object> builder = RequestContentDataExtractor.extract(this.request);
FormHttpOutputMessage data = new FormHttpOutputMessage();
this.contentType = MediaType.valueOf(this.request.getContentType());
data.getHeaders().setContentType(this.contentType);
FormBodyWrapperFilter.this.formHttpMessageConverter.write(builder, this.contentType, data);
// copy new content type including multipart boundary
this.contentType = data.getHeaders().getContentType();
byte[] input = data.getInput();
this.contentLength = input.length;
this.contentData = input;
}
catch (Exception e) {
throw new IllegalStateException("Cannot convert form data", e);
}
}
}
过滤器 | 类型 | order | 执行条件 | 描述 |
---|---|---|---|---|
DebugFilter | pre | 1 | 生效方式为zuul.debug.request参数配置为true或请求参数zuul.debug.parameter设置为true | 在上下文设置debugRouting,debugRequest参数为true |
@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();
ctx.setDebugRouting(true);
ctx.setDebugRequest(true);
return null;
}
- routeFilter相关类:sendFowrdFilter
过滤器 | 类型 | order | 执行条件 | 描述 |
---|---|---|---|---|
sendFowrdFilter | route | 500 | 上下文中存在forward.to值,且sendForwardFilter.ran未设置值或为false | 使用requestDispatcher 发送相关请求,在preDecorationFilter(@EnableZuulProxy注解引入)中会处理url为forward:开头的url地址,这部分配置forward:开头的请求会通过此过滤器转发请求。 |
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
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 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 filter相关类:sendReponseFilter
过滤器 | 类型 | order | 执行条件 | 描述 |
---|---|---|---|---|
sendReponseFilter | post | 1000 | 上下文中包含请求响应相关的头信息、响应数据、响应体中的任何一个且上下文中未被设置异常,设置异常了则会给sendErrorFilter处理 | 组装响应信息返回给客户端 |
@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 filter相关类:SendErrorFilter
过滤器 | 类型 | order | 执行条件 | 描述 |
---|---|---|---|---|
SendErrorFilter | error | 0 | 上下文中包含异常信息,且sendErrorFilter.ran不为true时执行 | 该类在1.4.4的版本中放在org.springframework.cloud.netflix.zuul.filters.post包下,此处不大规范,容易被误解成post过滤器。翻了下1.0.0的版本,SendErrorFilter类型为post类型。改成error过滤器后,结合zuulServlet中的各过滤器的执行逻辑顺序,异常处理会更加合理。error跟前面的response过滤器有个互斥条件ctx.getThrowable() ,所以最多只会执行一个 |
@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;
}
- 监控相关ZuulRefreshListener,观察者模式,监听applicaitonEvent事件。触发监听事件后,会做更新route的操作。
private static class ZuulRefreshListener
implements ApplicationListener<ApplicationEvent> {
@Autowired
private ZuulHandlerMapping zuulHandlerMapping;
private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent
|| event instanceof RefreshScopeRefreshedEvent
|| event instanceof RoutesRefreshedEvent) {
this.zuulHandlerMapping.setDirty(true);
}
else if (event instanceof HeartbeatEvent) {
if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) {
this.zuulHandlerMapping.setDirty(true);
}
}
}
}
实际项目过程中,HeartbeatEvent会频繁的 触发,原因是引入的spring-cloud-start-zuul包中包含eureka组件。引入该组件的工程服务,DiscoveryClient会每隔30s发心跳事件。心跳代码如下
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
- ZuulFilterConfiguration :初始化filters,该类的主要作用是把spring容器里面的所有zuulFilter类初始化到Map filters中。执行过滤器逻辑时会根据此filters找到相关的过滤器执行。典型的责任链模式。
@Configuration
protected static class ZuulFilterConfiguration {
@Autowired
private Map<String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer(
CounterFactory counterFactory, TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
}
}
以上是ZuulServerAutoConfiguration的主要相关内容,可以确认的是初始化了zuulProperties,各routeLocator,zuulController,zuulHandlerMapping,各zuulFilter。