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; } }