SpringCloud-源碼分析 zuul (一)

本文作者:陳剛,叩丁狼高級(jí)講師称近。原創(chuàng)文章刨秆,轉(zhuǎn)載請(qǐng)注明出處忆畅。

zuul舍命周期

zuul,在SpringCloud中充當(dāng)服務(wù)網(wǎng)關(guān)的角色缓醋,它包含了請(qǐng)求路由送粱,過濾驯镊,安全等功能竭鞍,可以說是我們web應(yīng)用的“安保人員”,保證了我們“微服務(wù)園區(qū)”的安全,那么zuul是如何實(shí)現(xiàn)路由和過濾等功能的呢偎快?我這里有一張摘抄于SpringCloud官網(wǎng)的zuul的生命周期圖片


叩丁狼教育.png

這張圖的大致流程為:
1.當(dāng)客戶端請(qǐng)求過來首先會(huì)到 "pre" filters 這樣的一個(gè)前置過濾器做一些處理洽胶,然后調(diào)用自定義的過濾器
2.前置過濾器執(zhí)行完了之后會(huì)調(diào)用 “routing”filter 過濾器 姊氓,看名字都知道這是做路由分發(fā)的過濾器
3.在路由的過程中出現(xiàn)了異常,那么會(huì)走 “error”filters過濾器读跷,然后再走 "post"filters 過濾器 禾唁,或者正常路由完成也會(huì)走到“post”filters
4."post"filters過濾器負(fù)責(zé)處理響應(yīng) ,最后把結(jié)果響應(yīng)給客戶端

這里是zuul大致的生命周期流程丐枉,我們看到它這里大量用到了filter進(jìn)行處理掘托,并且Zuul允許我們自定義Filter ,他提供了抽象的 ZuulFilter 過濾器弯院,里面有四個(gè)基本方法锭沟,我們要自定義Filter就需要繼承ZuulFilter,然后復(fù)寫四個(gè)方法

/**
 服務(wù)過濾
 */
@Component
public class MyFilter extends ZuulFilter {


    /**
        返回過濾器的類型,過濾器類型如下:
        pre:請(qǐng)求路由之前調(diào)用過濾
        routing:請(qǐng)求routing之時(shí)調(diào)用過濾
        post: 請(qǐng)求路由之后調(diào)用過濾
        error:發(fā)送錯(cuò)誤時(shí)調(diào)用過濾
     */
    @Override
    public String filterType() {
        return "pre";
    }

    //filterOrder:過濾的順序
    @Override
    public int filterOrder() {
        return 0;
    }

    //shouldFilter:是否要過濾祝辣,true表示永遠(yuǎn)過濾切油。我們可以在這里做一寫過濾處理
    @Override
    public boolean shouldFilter() {
        return true;
    }

 
  //當(dāng)前過濾器的執(zhí)行方法
  //我們可以在該方法中處理一些自己的判斷
    @Override
    public Object run() {
        //獲取請(qǐng)求對(duì)象
        RequestContext ctx = RequestContext.getCurrentContext();
        Object pass = ctx.getRequest().getParameter("pass");
        if(pass == null) {
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try {
                ctx.getResponse().getWriter().write("pass is empty");
            }catch (Exception e){}
        }
        return null;
    }
}

那么我們接下來就分析他的源碼就是去看這些內(nèi)置的filter做了什么事情。

zuul的啟動(dòng)/配置

簡(jiǎn)單回顧一下zuul的使用 孕荠,除了引入zuul相關(guān)依賴而外,我們要使用zull還需要在配置類上開啟zuul功能

//@EnableZuulProxy :開啟路由網(wǎng)關(guān)功能
@SpringBootApplication
@EnableZuulProxy
public class ServiceZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceZuulApplication.class, args);
    }
}

EnableZuulProxy的注釋告訴我們弯予,這里設(shè)設(shè)置Zuul服務(wù)器端點(diǎn),和安裝了一些過濾器个曙,通過這些過濾器它可以轉(zhuǎn)發(fā)請(qǐng)求到后端服務(wù)器

/**
 * Sets up a Zuul server endpoint and installs some reverse proxy filters in it, so it can
 * forward requests to backend servers. The backends can be registered manually through
 * configuration or via DiscoveryClient.
 *
 * @see EnableZuulServer for how to get a Zuul server without any proxying
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

不過這里引入了 ZuulProxyMarkerConfiguration 配置類,這個(gè)配置在干嘛呢呼寸?

/**
 * Responsible for adding in a marker bean to trigger activation of 
 * {@link ZuulProxyAutoConfiguration}
 *
 * @author Biju Kunjummen
 */

@Configuration
public class ZuulProxyMarkerConfiguration {
    @Bean
    public Marker zuulProxyMarkerBean() {
        return new Marker();
    }

    class Marker {
    }
}

翻譯:“Responsible for adding in a marker bean to trigger activation of

  • {@link ZuulProxyAutoConfiguration}”
    它在負(fù)責(zé)添加標(biāo)記bean以觸發(fā)激活 ZuulProxyAutoConfiguration 這個(gè)類猴贰,研究過springboot自動(dòng)配置的同學(xué)就會(huì)知道 ,SpringBoot 中會(huì)有大量的 xxxAutoConfiguration 自動(dòng)配置的類會(huì)在應(yīng)用啟動(dòng)的過程中被激活實(shí)現(xiàn)自動(dòng)裝配瑟捣,從而節(jié)省了我們很多的配置义郑。

而這個(gè)類在配置些什么東西?

/**
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@Configuration
//這里引入了幾種客戶端配置
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
        HttpClientConfiguration.class })
//如果存在了 ZuulProxyMarkerConfiguration.Marker的實(shí)例交汤,該配置生效芙扎,這里是滿足條件的
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
...省略代碼...

ZuulProxyAutoConfiguration繼承了 ZuulServerAutoConfiguration 填大,我們先看下這個(gè)配置類


/**
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@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
// FIXME @Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {
  //綁定zuul的配置信息
    @Autowired
    protected ZuulProperties zuulProperties;

    @Autowired
    protected ServerProperties server;

//注入請(qǐng)求錯(cuò)誤控制器
    @Autowired(required = false)
    private ErrorController errorController;

    @Bean
    public HasFeatures zuulFeature() {
        return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
    }

//RouteLocator that composes multiple RouteLocators. :
//多路由組合定位器
    @Bean
    @Primary
    public CompositeRouteLocator primaryRouteLocator(
            Collection<RouteLocator> routeLocators) {
        return new CompositeRouteLocator(routeLocators);
    }

//簡(jiǎn)單的路由定位器
    @Bean
    @ConditionalOnMissingBean(SimpleRouteLocator.class)
    public SimpleRouteLocator simpleRouteLocator() {
        return new SimpleRouteLocator(this.server.getServlet().getServletPrefix(),
                this.zuulProperties);
    }


    @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }

// MVC HandlerMapping that maps incoming request paths to remote services.
//看名字也知道允华,他是做請(qǐng)求路徑和遠(yuǎn)程服務(wù)的映射的,是  HandlerMapping的實(shí)現(xiàn)
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
        mapping.setErrorController(this.errorController);
        return mapping;
    }

//定義ZuulRefreshListener  zuul刷新的監(jiān)聽器
    @Bean
    public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
        return new ZuulRefreshListener();
    }

// Core Zuul servlet which intializes and orchestrates zuulFilter execution
//這里在注冊(cè)ZuulServlet 這樣的一個(gè)servlet, 這個(gè)東西了不得了磷蜀,
//他是負(fù)責(zé)核心Zuul servlet初始化和調(diào)用zuulFilter執(zhí)行百炬,跟DispatcherServlet差不過
    @Bean
    @ConditionalOnMissingBean(name = "zuulServlet")
    public ServletRegistrationBean zuulServlet() {
        ServletRegistrationBean<ZuulServlet> 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;
    }

    // pre filters  : 
//前置過濾器,看名字是用來做檢測(cè)的
    @Bean
    public ServletDetectionFilter servletDetectionFilter() {
        return new ServletDetectionFilter();
    }
//前置過濾器庶弃,是對(duì)請(qǐng)求數(shù)據(jù)做一些增強(qiáng)處理
    @Bean
    public FormBodyWrapperFilter formBodyWrapperFilter() {
        return new FormBodyWrapperFilter();
    }


    @Bean
    public DebugFilter debugFilter() {
        return new DebugFilter();
    }

    @Bean
    public Servlet30WrapperFilter servlet30WrapperFilter() {
        return new Servlet30WrapperFilter();
    }

    // post filters
       //下面是定義一系列的后置過濾器
    @Bean
    public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
        return new SendResponseFilter(zuulProperties);
    }

    @Bean
    public SendErrorFilter sendErrorFilter() {
        return new SendErrorFilter();
    }

    @Bean
    public SendForwardFilter sendForwardFilter() {
        return new SendForwardFilter();
    }           

整理一下這里配置類里面做了哪些事情呢?
1.注冊(cè)了多路由組合定位器 CompositeRouteLocator
2.注冊(cè)了簡(jiǎn)單的路由定位器SimpleRouteLocator
3.注冊(cè)了ZuulController 固惯,zuulServlet會(huì)通過調(diào)用它再掉伏,實(shí)現(xiàn)對(duì)請(qǐng)求的調(diào)用他的源碼如下

**
 * @author Spencer Gibb
 */
public class ZuulController extends ServletWrappingController {

    public ZuulController() {
        setServletClass(ZuulServlet.class);
        setServletName("zuul");
        setSupportedMethods((String[]) null); // Allow all
    }

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            // We don't care about the other features of the base class, just want to
            // handle the request
//處理請(qǐng)求
            return super.handleRequestInternal(request, response);
        }
        finally {
            // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
            RequestContext.getCurrentContext().unset();
        }
    }

}

4.注冊(cè)了ZuulHandlerMapping 是對(duì)path和遠(yuǎn)程服務(wù)的映射

  1. 注冊(cè)了zuulServlet : 請(qǐng)求的分發(fā)器類似于DispatcherServlet

6.定義了一系列的前置過濾器和后置過濾器,作用分別如下:

ServletDetectionFilter : 標(biāo)記處理servlet的類型供常,前置通知 鸡捐,執(zhí)行順序 -3

/**
 * Detects whether a request is ran through the {@link DispatcherServlet} or {@link ZuulServlet}.
 * The purpose was to detect this up-front at the very beginning of Zuul filter processing
 *  and rely on this information in all filters.
 *  RequestContext is used such that the information is accessible to classes 
 *  which do not have a request reference.
 * @author Adrian Ivan
 */
public class ServletDetectionFilter extends ZuulFilter {

    public ServletDetectionFilter() {
    }
//前置通知
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    /**
     * Must run before other filters that rely on the difference between 
     * DispatcherServlet and ZuulServlet.
     */
//filterOrder 決定了這個(gè)過濾器的執(zhí)行順序 這里是 :-3 見
//public static final int SERVLET_DETECTION_FILTER_ORDER = -3;
    @Override
    public int filterOrder() {
        return SERVLET_DETECTION_FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        return true; 
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
//判斷結(jié)果保存到  HttpServletRequest中
        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;
    }
    
//判斷當(dāng)前請(qǐng)求是否是DispatcherServletRequest
    private boolean isDispatcherServletRequest(HttpServletRequest request) {
        return request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;
    }       

翻譯過濾器上面的注釋"Detects whether a request is ran through the {@link DispatcherServlet} or {@link ZuulServlet}"大致功能為:用來標(biāo)記該請(qǐng)求是通過 DispatcherServlet處理還是通過 ZuulServlet處理
箍镜,run()把判斷結(jié)果以boolean值的方式保存到HttpServletRequest中,后續(xù)的處理中就可以通過它獲取到這個(gè)標(biāo)記做不同的處理,而這個(gè)filter執(zhí)行的順序是 -3(filterOrder() 方法) 香缺,越小越先執(zhí)行


FormBodyWrapperFilter :解析表單數(shù)據(jù)并為后續(xù)處理重新編碼歇僧,將符合要求的請(qǐng)求體包裝成FormBodyRequestWrapper對(duì)象。前置通知祸轮,執(zhí)行順序 -1

/**
 * Pre {@link ZuulFilter} that parses form data and reencodes it for downstream services
 *
 * @author Dave Syer
 */
public class FormBodyWrapperFilter extends ZuulFilter {
...省略...
@Override
    public String filterType() {
//前置通知
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
//執(zhí)行順序 -1
        return FORM_BODY_WRAPPER_FILTER_ORDER;
    }
@Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
//處理請(qǐng)求
        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 {
//包裝成 FormBodyRequestWrapper
            wrapper = new FormBodyRequestWrapper(request);
            ctx.setRequest(wrapper);
        }
        if (wrapper != null) {
            ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
        }
        return null;
    }
...省略...

DebugFilter :開啟調(diào)試標(biāo)記 侥钳,前置通知 ,執(zhí)行順序 1

/**
 * Pre {@link ZuulFilter} that sets {@link RequestContext} debug attributes to true if
 * the "debug" request parameter is set.
 *
 * @author Spencer Gibb
 */
public class DebugFilter extends ZuulFilter {

    private static final DynamicBooleanProperty ROUTING_DEBUG = DynamicPropertyFactory
            .getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, false);

    private static final DynamicStringProperty DEBUG_PARAMETER = DynamicPropertyFactory
            .getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "debug");

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return DEBUG_FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {
            return true;
        }
        return ROUTING_DEBUG.get();
    }

翻譯注釋“Pre {@link ZuulFilter} that sets {@link RequestContext} debug attributes to true if the "debug" request parameter is set.”
如果請(qǐng)求中設(shè)置了“debug”請(qǐng)求參數(shù)苦酱, RequestContext調(diào)試屬性設(shè)置為true躏啰。說白了就是通過 reques中的debug參數(shù)來激活調(diào)試信息,這樣當(dāng)線上環(huán)境出現(xiàn)問題的時(shí)候耙册,可以通過請(qǐng)求參數(shù)的方式來激活這些debug信息以幫助分析問題


Servlet30WrapperFilter :包裝http請(qǐng)求 详拙,前置通知 ,執(zhí)行順序 -2


/**
 * Pre {@link ZuulFilter} that wraps requests in a Servlet 3.0 compliant wrapper.
 * Zuul's default wrapper is only Servlet 2.5 compliant.
 * @author Spencer Gibb
 */
public class Servlet30WrapperFilter extends ZuulFilter {

    private Field requestField = null;

    public Servlet30WrapperFilter() {
        this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class,
                "req", HttpServletRequest.class);
        Assert.notNull(this.requestField,
                "HttpServletRequestWrapper.req field not found");
        this.requestField.setAccessible(true);
    }

    protected Field getRequestField() {
        return this.requestField;
    }

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return SERVLET_30_WRAPPER_FILTER_ORDER;
    }
@Override
    public Object run() {
//把請(qǐng)求包裝成  Servlet30RequestWrapper
        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;
    }

翻譯注釋:“that wraps requests in a Servlet 3.0 compliant wrapper.
Zuul's default wrapper is only Servlet 2.5 compliant.”
這里是對(duì)原始的HttpServletRequest請(qǐng)求包裝成Servlet30RequestWrapper對(duì)象即要兼容3.0蹲诀。zuul默認(rèn)只是兼容2.5,你現(xiàn)在知道為什么他叫 Servlet30WrapperFilter 了嗎脯爪?


SendResponseFilter :后置通知 ,處理請(qǐng)求響應(yīng),執(zhí)行順序 1000

/**
 * Post {@link ZuulFilter} that writes responses from proxied requests to the current response.
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Ryan Baxter
 */
public class SendResponseFilter extends ZuulFilter {
...省略...

@Override
    public Object run() {
        try {
            addResponseHeaders();
            writeResponse();
        }
        catch (Exception ex) {
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }


private void writeResponse() throws Exception {
        RequestContext context = RequestContext.getCurrentContext();
        // there is no body to send
        if (context.getResponseBody() == null
                && context.getResponseDataStream() == null) {
            return;
        }
        HttpServletResponse servletResponse = context.getResponse();
        if (servletResponse.getCharacterEncoding() == null) { // only set if not set
            servletResponse.setCharacterEncoding("UTF-8");
        }
        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;
            }
...省略...


//寫響應(yīng)結(jié)果
private void writeResponse(InputStream zin, OutputStream out) throws Exception {
        byte[] bytes = buffers.get();
        int bytesRead = -1;
        while ((bytesRead = zin.read(bytes)) != -1) {
            out.write(bytes, 0, bytesRead);
        }
    }

翻譯:that writes responses from proxied requests to the current response.
翻譯大致意思為把代理請(qǐng)求的響應(yīng)寫入到當(dāng)前響應(yīng)痕慢,
String body = RequestContext.getCurrentContext().getResponseBody(); 獲取到響應(yīng)內(nèi)容 涌矢,通過 servletResponse.getOutputStream(); 寫出去 ,
我們從源碼中可以看到該過濾器會(huì)檢查請(qǐng)求上下文中是否包含請(qǐng)求響應(yīng)相關(guān)的頭信息塔次、響應(yīng)數(shù)據(jù)流或是響應(yīng)體名秀,然后利用請(qǐng)求上下文的響應(yīng)信息來組織需要發(fā)送回客戶端的響應(yīng)內(nèi)容。


SendErrorFilter :錯(cuò)誤處理過濾器 继榆,把錯(cuò)誤重定向到/error路徑上耗跛,執(zhí)行順序 0


/**
 * Error {@link ZuulFilter} that forwards to /error (by default) if {@link RequestContext#getThrowable()} is not null.
 *
 * @author Spencer Gibb
 */
//TODO: move to error package in Edgware
public class SendErrorFilter extends ZuulFilter {

    private static final Log log = LogFactory.getLog(SendErrorFilter.class);
    protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";

//異常重定向路徑
    @Value("${error.path:/error}")
    private String errorPath;

    @Override
    public String filterType() {
        return ERROR_TYPE;
    }

    @Override
    public int filterOrder() {
        return SEND_ERROR_FILTER_ORDER;
    }

    @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();

//處理異常錯(cuò)誤碼等
            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;
    }

SendForwardFilter:用來處理路由規(guī)則中的forward本地跳轉(zhuǎn)配置 ,執(zhí)行順序 5000

/**
 * Route {@link ZuulFilter} that forwards requests using the {@link RequestDispatcher}.
 * Forwarding location is located in the {@link RequestContext} attribute {@link org.springframework.cloud.netflix.zuul.filters.support.FilterConstants#FORWARD_TO_KEY}.
 * Useful for forwarding to endpoints in the current application.
 用戶RequestDispatcher 進(jìn)行本地應(yīng)用端點(diǎn)的Forwarding
 * @author Dave Syer
 */
public class SendForwardFilter extends ZuulFilter {
...省略...
    @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()) {
//請(qǐng)求跳轉(zhuǎn)
                    dispatcher.forward(ctx.getRequest(), ctx.getResponse());
                    ctx.getResponse().flushBuffer();
                }
            }
        }
        catch (Exception ex) {
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }

你以為到這里就完了嗎调塌,并沒有,我們剛才看的是ZuulServerAutoConfiguration中定義的過濾器负间,在ZuulProxyAutoConfiguration中還定義了一些過濾器

/**
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
        HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
...省略代碼...
// pre filters  : 前置過濾
    @Bean
    public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {
        return new PreDecorationFilter(routeLocator, this.server.getServlet().getServletPrefix(), this.zuulProperties,
                proxyRequestHelper);
    }

    // route filters   :路由過濾
    @Bean
    public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
            RibbonCommandFactory<?> ribbonCommandFactory) {
        RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
                this.requestCustomizers);
        return filter;
    }

    @Bean
    @ConditionalOnMissingBean({SimpleHostRoutingFilter.class, CloseableHttpClient.class})
    public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
            ZuulProperties zuulProperties,
            ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
            ApacheHttpClientFactory httpClientFactory) {
        return new SimpleHostRoutingFilter(helper, zuulProperties,
                connectionManagerFactory, httpClientFactory);
    }

    @Bean
    @ConditionalOnMissingBean({SimpleHostRoutingFilter.class})
    public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
                                                           ZuulProperties zuulProperties,
                                                           CloseableHttpClient httpClient) {
        return new SimpleHostRoutingFilter(helper, zuulProperties,
                httpClient);
    }


PreDecorationFilter :匹配路由過著和服務(wù)位置政溃、在請(qǐng)求上下文中設(shè)置該請(qǐng)求的基本信息 态秧,執(zhí)行順序 5

/**
 * Pre {@link ZuulFilter} that determines where and how to route based on the supplied {@link RouteLocator}.
 * Also sets various proxy related headers for downstream requests.
 */
public class PreDecorationFilter extends ZuulFilter {
...省略...
@Override
    public Object run() {
//請(qǐng)求上下文
        RequestContext ctx = RequestContext.getCurrentContext();
//請(qǐng)求路徑
        final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
//根據(jù)請(qǐng)求地址,匹配匹配路由
        Route route = 
this.routeLocator.getMatchingRoute(requestURI);
        if (route != null) {
//從路由中獲取請(qǐng)求服務(wù)id
            String location = route.getLocation();
            if (location != null) {
//設(shè)置請(qǐng)求上下文相關(guān)信息
                ctx.put(REQUEST_URI_KEY, route.getPath());
                ctx.put(PROXY_KEY, route.getId());
                if (!route.isCustomSensitiveHeaders()) {
                    this.proxyRequestHelper
                            .addIgnoredHeaders(this.properties.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 null;
                }
                else {
              //設(shè)置服務(wù)id在RibbonReques中使用
                    // set serviceId for use in filters.route.RibbonRequest
                    ctx.set(SERVICE_ID_KEY, location);
                    ctx.setRouteHost(null);
                    ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
                }
                if (this.properties.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.properties.isAddHostHeader()) {
                    ctx.addZuulRequestHeader(HttpHeaders.HOST, toHostHeader(ctx.getRequest()));
                }

...省略...

RibbonRoutingFilter:routing過濾器云头,使用Ribbon和Hystrix來向服務(wù)實(shí)例發(fā)起請(qǐng)求 淫半,有服務(wù)熔斷機(jī)制,執(zhí)行順序 10


/**
 * Route {@link ZuulFilter} that uses Ribbon, Hystrix and pluggable http clients to send requests.
 * ServiceIds are found in the {@link RequestContext} attribute {@link org.springframework.cloud.netflix.zuul.filters.support.FilterConstants#SERVICE_ID_KEY}.
通過  Ribbon 和  Hystrix 向http客戶端發(fā)送請(qǐng)求
通過 RequestContext找到  ServiceIds服務(wù)id ,


 * @author Spencer Gibb
 * @author Dave Syer
 * @author Ryan Baxter
 */
public class RibbonRoutingFilter extends ZuulFilter {
...省略...

@Override
    public Object run() {
//獲取請(qǐng)求上下文 
        RequestContext context = RequestContext.getCurrentContext();
        this.helper.addIgnoredHeaders();
        try {

//創(chuàng)建一個(gè) RibbonCommandContext Ribbon命令上下文昏滴,用來發(fā)請(qǐng)求
            RibbonCommandContext commandContext = buildCommandContext(context);
//發(fā)送請(qǐng)求对人,獲取到結(jié)果
            ClientHttpResponse response = forward(commandContext);
            setResponse(response);
            return response;
        }
        catch (ZuulException ex) {
            throw new ZuulRuntimeException(ex);
        }
        catch (Exception ex) {
            throw new ZuulRuntimeException(ex);
        }
    }

//根據(jù)RequestContext 請(qǐng)求上下文,獲取請(qǐng)求服務(wù)id蟹倾,url等封裝成RibbonCommandContext
protected RibbonCommandContext buildCommandContext(RequestContext context) {
        HttpServletRequest request = context.getRequest();

        MultiValueMap<String, String> headers = this.helper
                .buildZuulRequestHeaders(request);
        MultiValueMap<String, String> params = this.helper
                .buildZuulRequestQueryParams(request);
        String verb = getVerb(request);
        InputStream requestEntity = getRequestBody(request);
        if (request.getContentLength() < 0 && !verb.equalsIgnoreCase("GET")) {
            context.setChunkedRequestBody();
        }

        String serviceId = (String) context.get(SERVICE_ID_KEY);
        Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
        Object loadBalancerKey = context.get(LOAD_BALANCER_KEY);

        String uri = this.helper.buildZuulRequestURI(request);

        // remove double slashes
        uri = uri.replace("http://", "/");

        long contentLength = useServlet31 ? request.getContentLengthLong(): request.getContentLength();

        return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
                requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
    }

protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
        Map<String, Object> info = this.helper.debug(context.getMethod(),
                context.getUri(), context.getHeaders(), context.getParams(),
                context.getRequestEntity());

        RibbonCommand command = this.ribbonCommandFactory.create(context);
        try {
//執(zhí)行請(qǐng)求
            ClientHttpResponse response = command.execute();
            this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
            return response;
        }
        catch (HystrixRuntimeException ex) {
//處理異常
            return handleException(info, ex);
        }

    }
//處理異常
protected ClientHttpResponse handleException(Map<String, Object> info,
            HystrixRuntimeException ex) throws ZuulException {
        int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
        Throwable cause = ex;
        String message = ex.getFailureType().toString();

        ClientException clientException = findClientException(ex);
        if (clientException == null) {
            clientException = findClientException(ex.getFallbackException());
        }

        if (clientException != null) {
            if (clientException
                    .getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
                statusCode = HttpStatus.SERVICE_UNAVAILABLE.value();
            }
            cause = clientException;
            message = clientException.getErrorType().toString();
        }
        info.put("status", String.valueOf(statusCode));
        throw new ZuulException(cause, "Forwarding error", statusCode, message);
    }


SimpleHostRoutingFilter : 通過RequestContext#getRouteHost()找到調(diào)用的服務(wù)地址 鲜棠,使用http客戶端實(shí)現(xiàn)調(diào)用 ,他和RibbonRoutingFilter的區(qū)別是沒有使用Hystrix所以并沒有線程隔離和斷路器的保護(hù)培慌。
執(zhí)行順序 100

/**
 * Route {@link ZuulFilter} that sends requests to predetermined URLs via apache
 * {@link HttpClient}. URLs are found in {@link RequestContext#getRouteHost()}.

通過RequestContext#getRouteHost()找到調(diào)用的服務(wù)地址 ,使用apache的http客戶端實(shí)現(xiàn)調(diào)用
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Bilal Alp
 * @author Gang Li
 */
public class SimpleHostRoutingFilter extends ZuulFilter {

...省略...
@Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        MultiValueMap<String, String> headers = this.helper
                .buildZuulRequestHeaders(request);
        MultiValueMap<String, String> params = this.helper
                .buildZuulRequestQueryParams(request);
        String verb = getVerb(request);
        InputStream requestEntity = getRequestBody(request);
        if (request.getContentLength() < 0) {
            context.setChunkedRequestBody();
        }

//獲取請(qǐng)求地址
        String uri = this.helper.buildZuulRequestURI(request);
        this.helper.addIgnoredHeaders();

        try {
//發(fā)送請(qǐng)求
            CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
                    headers, params, requestEntity);
            setResponse(response);
        }
        catch (Exception ex) {
            throw new ZuulRuntimeException(ex);
        }
        return null;
    }
------------------------------------
//使用 httpclient 發(fā)送請(qǐng)求
private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb,
            String uri, HttpServletRequest request, MultiValueMap<String, String> headers,
            MultiValueMap<String, String> params, InputStream requestEntity)
            throws Exception {
        Map<String, Object> info = this.helper.debug(verb, uri, headers, params,
                requestEntity);
//請(qǐng)求主機(jī)
        URL host = RequestContext.getCurrentContext().getRouteHost();
        HttpHost httpHost = getHttpHost(host);
//請(qǐng)求地址
        uri = StringUtils.cleanPath((host.getPath() + uri).replaceAll("/{2,}", "/"));
        int contentLength = request.getContentLength();

        ContentType contentType = null;

        if (request.getContentType() != null) {
            contentType = ContentType.parse(request.getContentType());
        }

        InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength,
                contentType);

        HttpRequest httpRequest = buildHttpRequest(verb, uri, entity, headers, params,
                request);
        try {
            log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " "
                    + httpHost.getSchemeName());
//發(fā)送請(qǐng)求
            CloseableHttpResponse zuulResponse = forwardRequest(httpclient, httpHost,
                    httpRequest);
            this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(),
                    revertHeaders(zuulResponse.getAllHeaders()));
            return zuulResponse;
        }
        finally {
            // When HttpClient instance is no longer needed,
            // shut down the connection manager to ensure
            // immediate deallocation of all system resources
            // httpclient.getConnectionManager().shutdown();
        }
    }
------------------------------------
protected HttpRequest buildHttpRequest(String verb, String uri,
            InputStreamEntity entity, MultiValueMap<String, String> headers,
            MultiValueMap<String, String> params, HttpServletRequest request) {
        HttpRequest httpRequest;
        String uriWithQueryString = uri + (this.forceOriginalQueryStringEncoding
                ? getEncodedQueryString(request) : 

this.helper.getQueryString(params));
//處理各種請(qǐng)求方式
        switch (verb.toUpperCase()) {
        case "POST":
            HttpPost httpPost = new HttpPost(uriWithQueryString);
            httpRequest = httpPost;
            httpPost.setEntity(entity);
            break;
        case "PUT":
            HttpPut httpPut = new HttpPut(uriWithQueryString);
            httpRequest = httpPut;
            httpPut.setEntity(entity);
            break;
        case "PATCH":
            HttpPatch httpPatch = new HttpPatch(uriWithQueryString);
            httpRequest = httpPatch;
            httpPatch.setEntity(entity);
            break;
        case "DELETE":
            BasicHttpEntityEnclosingRequest entityRequest = new BasicHttpEntityEnclosingRequest(
                    verb, uriWithQueryString);
            httpRequest = entityRequest;
            entityRequest.setEntity(entity);
            break;
        default:
            httpRequest = new BasicHttpRequest(verb, uriWithQueryString);
            log.debug(uriWithQueryString);
        }

        httpRequest.setHeaders(convertHeaders(headers));
        return httpRequest;
    }
------------------------------------
//最終執(zhí)行請(qǐng)求
private CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient,
            HttpHost httpHost, HttpRequest httpRequest) throws IOException {
        return httpclient.execute(httpHost, httpRequest);
    }


到這里我們把 ZuulProxyAutoConfiguration 自動(dòng)配置類中定義的比較重要的一些過濾器都介紹完了 ,zuul在執(zhí)行過程中就會(huì)按照這些filter的調(diào)用順序去執(zhí)行馅而,我們來用表格整理一下

用表格

那這一章我們分析到這里 ,下一章我們跟蹤一下zuul的執(zhí)行流程雄坪,看他是如果把這些filter串聯(lián)起來的 屯蹦。

想獲取更多技術(shù)干貨,請(qǐng)前往叩丁狼官網(wǎng):http://www.wolfcode.cn/all_article.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末登澜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子购撼,更是在濱河造成了極大的恐慌,老刑警劉巖份招,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡哼审,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門十气,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砸西,你說我怎么就攤上這事∏奂希” “怎么了莲趣?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)喧伞。 經(jīng)常有香客問我,道長(zhǎng)翁逞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任挖函,我火速辦了婚禮彼念,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逐沙。我一直安慰自己,他們只是感情好吩案,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著靠益,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胧后。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天纸巷,我揣著相機(jī)與錄音,去河邊找鬼瘤旨。 笑死竖伯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的七婴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼本姥,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了氛赐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤艰管,失蹤者是張志新(化名)和其女友劉穎蒋川,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捺球,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年裂逐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泣栈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弥姻。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掺涛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出薪缆,到底是詐尸還是另有隱情,我是刑警寧澤矮燎,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響灾票,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刊苍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望正什。 院中可真熱鬧,春花似錦婴氮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽穗酥。三九已至,卻和暖如春砾跃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抽高。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工课锌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祈秕,地道東北人雏胃。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瞭亮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子统翩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容