在zuul中, 全体要求的过程是这样的,首先将要求给zuulservlet处理,zuulservlet中有一个zuulRunner工具,该工具中初始化了RequestContext:作为存储全体要求的一些数据,并被所有的zuulfilter共享。zuulRunner中还有 FilterProcessor,FilterProcessor作为实行所有的zuulfilter的管理器。FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支持groovy热加载,采取了轮询的办法热加载。
有了这些filter之后,zuulservelet首先实行的Pre类型的过滤器,再实行route类型的过滤器,末了实行的是post 类型的过滤器,如果在实行这些过滤器有缺点的时候则会实行error类型的过滤器。实行完这些过滤器,终极将要求的结果返回给客户端。
zuul事情事理源码剖析

在之前已经讲过,如何利用zuul,个中不可短缺的一个步骤便是在程序的启动类加上@EnableZuulProxy,该EnableZuulProxy类代码如下:
@EnableCircuitBreaker@EnableDiscoveryClient@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Import(ZuulProxyConfiguration.class)public @interface EnableZuulProxy {}
个中,引用了ZuulProxyConfiguration,跟踪ZuulProxyConfiguration,该类注入了DiscoveryClient、RibbonCommandFactoryConfiguration用作负载均衡干系的。注入了一些列的filters,比如PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter,代码如如下:
@Beanpublic PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(), this.zuulProperties,proxyRequestHelper);}// route filters@Beanpublic RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,RibbonCommandFactory<?> ribbonCommandFactory) {RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers);return filter;}@Beanpublic SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties) {return new SimpleHostRoutingFilter(helper, zuulProperties);}
它的父类ZuulConfiguration ,引用了一些干系的配置。在缺失落zuulServlet bean的情形下注入了ZuulServlet,该类是zuul的核心类。
@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;}
同时也注入了其他的过滤器,比如ServletDetectionFilter、DebugFilter、Servlet30WrapperFilter,这些过滤器都是pre类型的。
@Beanpublic ServletDetectionFilter servletDetectionFilter() {return new ServletDetectionFilter();}@Beanpublic FormBodyWrapperFilter formBodyWrapperFilter() {return new FormBodyWrapperFilter();}@Beanpublic DebugFilter debugFilter() {return new DebugFilter();}@Beanpublic Servlet30WrapperFilter servlet30WrapperFilter() {return new Servlet30WrapperFilter();}
它也注入了post类型的,比如 SendResponseFilter,error类型,比如 SendErrorFilter,route类型比如SendForwardFilter,代码如下:
@Beanpublic SendResponseFilter sendResponseFilter() {return new SendResponseFilter();}@Beanpublic SendErrorFilter sendErrorFilter() {return new SendErrorFilter();}@Beanpublic SendForwardFilter sendForwardFilter() {return new SendForwardFilter();}
初始化ZuulFilterInitializer类,将所有的filter 向FilterRegistry注册。
@Configurationprotected static class ZuulFilterConfiguration {@Autowiredprivate Map<String, ZuulFilter> filters;@Beanpublic ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory, TracerFactory tracerFactory) {FilterLoader filterLoader = FilterLoader.getInstance();FilterRegistry filterRegistry = FilterRegistry.instance();return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);}}
而FilterRegistry管理了一个ConcurrentHashMap,用作存储过滤器的,并有一些基本的CURD过滤器的方法,代码如下:
public class FilterRegistry { private static final FilterRegistry INSTANCE = new FilterRegistry(); public static final FilterRegistry instance() { return INSTANCE; } private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>(); private FilterRegistry() { } public ZuulFilter remove(String key) { return this.filters.remove(key); } public ZuulFilter get(String key) { return this.filters.get(key); } public void put(String key, ZuulFilter filter) { this.filters.putIfAbsent(key, filter); } public int size() { return this.filters.size(); } public Collection<ZuulFilter> getAllFilters() { return this.filters.values(); }}
FilterLoader类持有FilterRegistry,FilterFileManager类持有FilterLoader,以是终极是由FilterFileManager注入 filterFilterRegistry的ConcurrentHashMap的。FilterFileManager到开启了轮询机制,定时的去加载过滤器,代码如下:
void startPoller() {
poller = new Thread(\"大众GroovyFilterFileManagerPoller\"大众) {
public void run() {
while (bRunning) {
try {
sleep(pollingIntervalSeconds 1000);
manageFiles();
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
poller.setDaemon(true);
poller.start();
}
Zuulservlet作为类似于Spring MVC中的DispatchServlet,起到了前端掌握器的浸染,所有的要求都由它接管。它的核心代码如下:
@Override 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(); } }
跟踪init(),可以创造这个方法为每个要求天生了RequestContext,RequestContext继续了ConcurrentHashMap<String, Object>,在要求结束时销毁掉该RequestContext,RequestContext的生命周期为要求到zuulServlet开始处理,直到要求结束返回结果。 RequestContext类在存储了很多主要的信息,包括HttpServletRequest、HttpServletRespons、ResponseDataStream、ResponseStatusCode等。 RequestContext工具在处理要求的过程中,一贯存在,以是这个工具为所有Filter共享。
从ZuulServlet的service()方法可知,它是先处理pre()类型的处理器,然后在处理route()类型的处理器,末了再处理post类型的处理器。
首先来看一看pre()的处理过程,它会进入到ZuulRunner,该类的浸染是将要求的HttpServletRequest、HttpServletRespons放在RequestContext类中,并包装了一个FilterProcessor,代码如下:
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)); } public void preRoute() throws ZuulException { FilterProcessor.getInstance().preRoute();}
而FilterProcessor类为调用filters的类,比如调用pre类型所有的过滤器:
public void preRoute() throws ZuulException { try { runFilters(\"大众pre\"大众); } catch (ZuulException e) { throw e; } catch (Throwable e) { throw new ZuulException(e, 500, \"大众UNCAUGHT_EXCEPTION_IN_PRE_FILTER_\"大众 + e.getClass().getName()); } }
跟踪runFilters()方法,可以创造,它终极调用了FilterLoader的getFiltersByType(sType)方法来获取同一类的过滤器,然后用for循环遍历所有的ZuulFilter,实行了 processZuulFilter()方法,跟踪该方法可以创造终极是实行了ZuulFilter的方法,终极返回了该方法返回的Object工具。
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug(\公众Invoking {\"大众 + sType + \"大众} type filters\公众);
}
boolean bResult = false;
List<ZuulFilter> 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;
}
route、post类型的过滤器的实行过程和pre实行过程类似。
Zuul默认过滤器
默认的核心过滤器一览表
Zuul默认注入的过滤器,它们的实行顺序在FilterConstants类,我们可以先定位在这个类,然后再看这个类的过滤器的实行顺序以及干系的注释,可以很轻松定位到干系的过滤器,也可以直接打开 spring-cloud-netflix-core.jar的 zuul.filters包,可以看到一些列的filter,现在我以表格的形式,列出默认注入的filter.
过滤器order描述类型ServletDetectionFilter-3检测要求是用 DispatcherServlet还是 ZuulServletpreServlet30WrapperFilter-2在Servlet 3.0 下,包装 requestspreFormBodyWrapperFilter-1解析表单数据preSendErrorFilter0如果中途涌现缺点errorDebugFilter1设置要求过程是否开启debugprePreDecorationFilter5根据uri决定调用哪一个route过滤器preRibbonRoutingFilter10如果写配置的时候用ServiceId则用这个route过滤器,该过滤器可以用Ribbon 做负载均衡,用hystrix做熔断routeSimpleHostRoutingFilter100如果写配置的时候用url则用这个route过滤routeSendForwardFilter500用RequestDispatcher要求转发routeSendResponseFilter1000用RequestDispatcher要求转发post
过滤器的order值越小,就越先实行,并且在实行过滤器的过程中,它们共享了一个RequestContext工具,该工具的生命周期贯穿于要求,可以看出优先实行了pre类型的过滤器,并将实行后的结果放在RequestContext中,供后续的filter利用,比如在实行PreDecorationFilter的时候,决定利用哪一个route,它的结果的是放在RequestContext工具中,后续会实行所有的route的过滤器,如果不知足条件就不实行该过滤器的run方法。终极达到了就实行一个route过滤器的run()方法。
而error类型的过滤器,是在程序发生非常的时候实行的。
post类型的过滤,在默认的情形下,只注入了SendResponseFilter,该类型的过滤器是将终极的要求结果以流的形式输出给客户单。
现在来看SimpleHostRoutingFilter是如何事情?
进入到SimpleHostRoutingFilter类的方法的run()方法,核心代码如下:
@Overridepublic Object run() {RequestContext context = RequestContext.getCurrentContext(); //省略代码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作为要求类,并重构了url,要求到了详细的做事,得到的一个CloseableHttpResponse工具,并将CloseableHttpResponse工具的保存到RequestContext工具中。并调用了ProxyRequestHelper的setResponse方法,将要求状态码,流等信息保存在RequestContext工具中。
private void setResponse(HttpResponse response) throws IOException {RequestContext.getCurrentContext().set(\"大众zuulResponse\"大众, response);this.helper.setResponse(response.getStatusLine().getStatusCode(),response.getEntity() == null ? null : response.getEntity().getContent(),revertHeaders(response.getAllHeaders()));}
现在来看SendResponseFilter是如何事情?
这个过滤器的order为1000,在默认且正常的情形下,是末了一个实行的过滤器,该过滤器是终极将得到的数据返回给客户真个要求。
在它的run()方法里,有两个方法:addResponseHeaders()和writeResponse(),即添加相应头和写入相应数据流。
public Object run() {
try {
addResponseHeaders();
writeResponse();
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
个中writeResponse()方法是通过从RequestContext中获取ResponseBody获或者ResponseDataStream来写入到HttpServletResponse中的,但是在默认的情形下ResponseBody为null,而ResponseDataStream在route类型过滤器中已经设置进去了。详细代码如下:
private void writeResponse() throws Exception {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
//代码省略
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;
}
//代码省略
is = context.getResponseDataStream();
InputStream inputStream = is;
//代码省略
writeResponse(inputStream, outStream);
//代码省略
}
}
..//代码省略
}
如何在zuul上做日志处理
由于zuul作为api网关,所有的要求都经由这里,以是在网关上,可以做要求干系的日志处理。 我的需求是这样的,须要记录要求的 url,ip地址,参数,要求发生的韶光,全体要求的耗时,要求的相应状态,乃至要求相应的结果等。 很显然,须要实现这样的一个功能,须要写一个ZuulFliter,它该当是在要求发送给客户端之前做处理,并且在route过滤器路由之后,在默认的情形下,这个过滤器的order该当为500-1000之间。那么如何获取这些我须要的日志信息呢?找RequestContext,在要求的生命周期里这个工具里,存储了全体要求的所有信息。
现在编码,在代码的注释中,做了详细的解释,代码如下:
@Componentpublic class LoggerFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.POST_TYPE; } @Override public int filterOrder() { return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); String method = request.getMethod();//氢气的类型,post get .. Map<String, String> params = HttpUtils.getParams(request); String paramsStr = params.toString();//要求的参数 long statrtTime = (long) context.get(\公众startTime\"大众);//要求的开始韶光 Throwable throwable = context.getThrowable();//要求的非常,如果有的话 request.getRequestURI();//要求的uri HttpUtils.getIpAddress(request);//要求的iP地址 context.getResponseStatusCode();//要求的状态 long duration=System.currentTimeMillis() - statrtTime);//要求耗时 return null; }}
现在读者大概有疑问,如何得到的statrtTime,即要求开始的韶光,实在这须要其余一个过滤器,在网络要求route之前(大部分耗时都在route这一步),在过滤器中,在RequestContext存储一个韶光即可,另写一个过滤器,代码如下:
@Componentpublic class AccessFilter extends ZuulFilter { @Override public String filterType() { return \"大众pre\"大众; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); ctx.set(\"大众startTime\"大众,System.currentTimeMillis()); return null; }}
可能还有这样的需求,我须要将相应结果,也要存储在log中,在之前已经剖析了,在route结束后,将从详细做事获取的相应流存储在RequestContext中,在SendResponseFilter过滤器写入在HttpServletResponse中,终极返回给客户端。那么我只须要在SendResponseFilter写入相应流之前把相应流写入到 log日志中即可,那么会引发其余一个问题,由于相应流写入到 log后,RequestContext就没有相应流了,在SendResponseFilter就没有流输入到HttpServletResponse中,导致客户端没有任何的返回数据,那么办理的办法是这样的:
InputStream inputStream =RequestContext.getCurrentContext().getResponseDataStream();InputStream newInputStream= copy(inputStream);transerferTolog(inputStream);RequestContext.getCurrentContext().setResponseDataStream(newInputStream);
从RequestContext获取到流之后,首先将流 copy一份,将流转化下字符串,存在日志中,再set到RequestContext中, 这样SendResponseFilter就可以将相应返回给客户端。这样的做法有点影响性能,如果不是字符流,可能须要做更多的处理事情。