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

請求主要是在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;
    }
}
prefilter

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

}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM