Zuul 源码的分析
Zuul 就是个网关,过滤所有数据, 和Eureka的区别就是,前者或过滤数据,一般进行权限拦截,后者进行请求的转发,只是链接。
Zuul包含了对请求的路由和过滤两个最主要的功能:
使用 注解@EnableZuulProxy 引入 ZuulProxyMarkerConfiguration.class
此时导入的配置类也会注入
@Configuration @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class, RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class, RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class }) @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class) public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
这个配置类中主要注入了已写filter和controller之类的,具体看源码
@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; }
初始化ZuulFilterInitializer类,将所有的filter 向FilterRegistry注册。
@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();
//FilterRegistry管理了一个ConcurrentHashMap,用作存储过滤器的 return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry); } }
在zuul中, 整个请求的过程是这样的,首先将请求给zuulservlet处理,zuulservlet中有一个zuulRunner对象,该对象中初始化了RequestContext:作为存储整个请求的一些数据,并被所有的zuulfilter共享。zuulRunner中还有 FilterProcessor,FilterProcessor作为执行所有的zuulfilter的管理器。FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支持groovy热加载,采用了轮询的方式热加载。有了这些filter之后,zuulservelet首先执行的Pre类型的过滤器,再执行route类型的过滤器,最后执行的是post 类型的过滤器,如果在执行这些过滤器有错误的时候则会执行error类型的过滤器。执行完这些过滤器,最终将请求的结果返回给客户端。
ZuulServlet初始化zuulRunner
@Override public void init(ServletConfig config) throws ServletException { super.init(config); String bufferReqsStr = config.getInitParameter("buffer-requests"); boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false; zuulRunner = new ZuulRunner(bufferReqs); }
zuulRunner初始化RequestContext
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)); }
FilterProcessor过滤器处理器:
ZuulFilter主要分类有四种:
PRE: 该类型的filters在Request routing到源web-service之前执行。用来实现Authentication、选择源服务地址等
ROUTING:该类型的filters用于把Request routing到源web-service,源web-service是实现业务逻辑的服务。这里使用HttpClient请求web-service。
POST:该类型的filters在ROUTING返回Response后执行。用来实现对Response结果进行修改,收集统计数据以及把Response传输会客户端。
ERROR:上面三个过程中任何一个出现错误都交由ERROR类型的filters进行处理。
{ pre=[ org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter@665cb192, org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter@a3739e1, org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter@69676b9c, org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter@666e1965, com.qinsilk.cloud.gateway.filter.AccessFilter@7da324d0, com.qinsilk.cloud.gateway.filter.OAuthFilter@2d86f17b, com.qinsilk.cloud.gateway.filter.DocumentFilter@70a416c, org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter@4a105d1b, com.自定义.filter.StaticResponseFilter@4047ca4d ], route=[ org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter@6bf6b454, org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter@72a14347, org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter@69f4143 ], post=[ org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter@717616da ] }
过滤器 | order | 描述 | 类型 |
---|---|---|---|
ServletDetectionFilter | -3 | 检测请求是用 DispatcherServlet还是 ZuulServlet | pre |
Servlet30WrapperFilter | -2 | 在Servlet 3.0 下,包装 requests | pre |
FormBodyWrapperFilter | -1 | 解析表单数据 | pre |
SendErrorFilter | 0 | 如果中途出现错误 | error |
DebugFilter | 1 | 设置请求过程是否开启debug | pre |
PreDecorationFilter | 5 | 根据uri决定调用哪一个route过滤器 | pre |
RibbonRoutingFilter | 10 | 如果写配置的时候用ServiceId则用这个route过滤器,该过滤器可以用Ribbon 做负载均衡,用hystrix做熔断 | route |
SimpleHostRoutingFilter | 100 | 如果写配置的时候用url则用这个route过滤 | route |
SendForwardFilter | 500 | 用RequestDispatcher请求转发 | route |
SendResponseFilter | 1000 | 用RequestDispatcher请求转发 | post |
自定义的filter实现ZuulFilter 或者想在哪个filter前可配置
public abstract class PreDescorationBaseFilter extends ZuulFilter{ @Autowired protected ZuulProperties zuulProperties; @Resource(name = "primaryRouteLocator") private RouteLocator routeLocator; @Resource private PreDecorationFilter preDecorationFilter; private ProxyRequestHelper proxyRequestHelper = new ProxyRequestHelper(); protected void router(RequestContext ctx, String newUri) { Route route = this.routeLocator.getMatchingRoute(newUri); if (route != null) { String location = route.getLocation(); if (location != null) { ctx.put(REQUEST_URI_KEY, route.getPath()); ctx.put(PROXY_KEY, route.getId()); if (!route.isCustomSensitiveHeaders()) { this.proxyRequestHelper.addIgnoredHeaders(this.zuulProperties.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 ; } else { ctx.set(SERVICE_ID_KEY, location); ctx.setRouteHost(null); ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location); } if (this.zuulProperties.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.zuulProperties.isAddHostHeader()) { ctx.addZuulRequestHeader(HttpHeaders.HOST, toHostHeader(ctx.getRequest())); } } } } protected URL getUrl(String target) { try { return new URL(target); } catch (MalformedURLException ex) { throw new IllegalStateException("Target URL is malformed", ex); } } protected void addProxyHeaders(RequestContext ctx, Route route) { HttpServletRequest request = ctx.getRequest(); String host = toHostHeader(request); String port = String.valueOf(request.getServerPort()); String proto = request.getScheme(); if (hasHeader(request, X_FORWARDED_HOST_HEADER)) { host = request.getHeader(X_FORWARDED_HOST_HEADER) + "," + host; } if (!hasHeader(request, X_FORWARDED_PORT_HEADER)) { if (hasHeader(request, X_FORWARDED_PROTO_HEADER)) { StringBuilder builder = new StringBuilder(); for (String previous : StringUtils.commaDelimitedListToStringArray(request.getHeader(X_FORWARDED_PROTO_HEADER))) { if (builder.length()>0) { builder.append(","); } builder.append(HTTPS_SCHEME.equals(previous) ? HTTPS_PORT : HTTP_PORT); } builder.append(",").append(port); port = builder.toString(); } } else { port = request.getHeader(X_FORWARDED_PORT_HEADER) + "," + port; } if (hasHeader(request, X_FORWARDED_PROTO_HEADER)) { proto = request.getHeader(X_FORWARDED_PROTO_HEADER) + "," + proto; } ctx.addZuulRequestHeader(X_FORWARDED_HOST_HEADER, host); ctx.addZuulRequestHeader(X_FORWARDED_PORT_HEADER, port); ctx.addZuulRequestHeader(X_FORWARDED_PROTO_HEADER, proto); addProxyPrefix(ctx, route); } protected void addProxyPrefix(RequestContext ctx, Route route) { String forwardedPrefix = ctx.getRequest().getHeader(X_FORWARDED_PREFIX_HEADER); String contextPath = ctx.getRequest().getContextPath(); String prefix = StringUtils.hasLength(forwardedPrefix) ? forwardedPrefix : (StringUtils.hasLength(contextPath) ? contextPath : null); if (StringUtils.hasText(route.getPrefix())) { StringBuilder newPrefixBuilder = new StringBuilder(); if (prefix != null) { if (prefix.endsWith("/") && route.getPrefix().startsWith("/")) { newPrefixBuilder.append(prefix, 0, prefix.length() - 1); } else { newPrefixBuilder.append(prefix); } } newPrefixBuilder.append(route.getPrefix()); prefix = newPrefixBuilder.toString(); } if (prefix != null) { ctx.addZuulRequestHeader(X_FORWARDED_PREFIX_HEADER, prefix); } } protected boolean hasHeader(HttpServletRequest request, String name) { return StringUtils.hasLength(request.getHeader(name)); } protected String toHostHeader(HttpServletRequest request) { int port = request.getServerPort(); if ((port == HTTP_PORT && HTTP_SCHEME.equals(request.getScheme())) || (port == HTTPS_PORT && HTTPS_SCHEME.equals(request.getScheme()))) { return request.getServerName(); } else { return request.getServerName() + ":" + port; } } }
请求主要是在routefilter中过滤执行到 SimpleHostRoutingFilter
在zuul上做日志处理
记录请求的 url,ip地址,参数,请求发生的时间,整个请求的耗时,请求的响应状态,甚至请求响应的结果等,需要写一个ZuulFliter,它应该是在请求发送给客户端之前做处理,并且在route过滤器路由之后.
记录开始时间filter
@Component public 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; } }
结束logfilter
@Component public 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; } }