58°

Spring zuul 快速入门实践 --服务转发实现解析

  zuul 作为springCloud 的全家桶组件之一,有着不可或缺的分量。它作为一个普通java API网关,自有网关的好处:

    避免将内部信息暴露给外部;
    统一服务端应用入口;
    为微服务添加额外的安全层;
    支持混合通信协议;
    降低构建微服务的复杂性;
    微服务模拟与虚拟化;

  zuul 基本上已经被springCloud 处理为一个开箱即用的一个组件了,所以基本上只需要添加相应依赖和一些必要配置,该网关就可以跑起来了。(这和nginx反该功能看起来是差不多的)

  让我们来快速实践一下吧!

 

一、zuul入坑基本实践步骤

1.1. 引入 pom 依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
<span style="color: #0000ff;">&lt;</span><span style="color: #800000;">modelVersion</span><span style="color: #0000ff;">&gt;</span>4.0.0<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">modelVersion</span><span style="color: #0000ff;">&gt;</span>
<span style="color: #0000ff;">&lt;</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">&gt;</span>zuul-test<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">&gt;</span>
<span style="color: #0000ff;">&lt;</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">&gt;</span>com.youge<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">&gt;</span>
<span style="color: #0000ff;">&lt;</span><span style="color: #800000;">version</span><span style="color: #0000ff;">&gt;</span>1.0<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">version</span><span style="color: #0000ff;">&gt;</span>

<span style="color: #008000;">&lt;!--</span><span style="color: #008000;"> 引入spingcloud 全家桶 </span><span style="color: #008000;">--&gt;</span>
<span style="color: #0000ff;">&lt;</span><span style="color: #800000;">dependencyManagement</span><span style="color: #0000ff;">&gt;</span>
    <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">dependencies</span><span style="color: #0000ff;">&gt;</span>
        <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">&gt;</span>
            <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">&gt;</span>org.springframework.cloud<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">&gt;</span>
            <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">&gt;</span>spring-cloud-dependencies<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">&gt;</span>
            <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">version</span><span style="color: #0000ff;">&gt;</span>Finchley.RC2<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">version</span><span style="color: #0000ff;">&gt;</span>
            <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">type</span><span style="color: #0000ff;">&gt;</span>pom<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">type</span><span style="color: #0000ff;">&gt;</span>
            <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">scope</span><span style="color: #0000ff;">&gt;</span>import<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">scope</span><span style="color: #0000ff;">&gt;</span>
        <span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">&gt;</span>
    <span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">dependencies</span><span style="color: #0000ff;">&gt;</span>
<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">dependencyManagement</span><span style="color: #0000ff;">&gt;</span>

<span style="color: #0000ff;">&lt;</span><span style="color: #800000;">dependencies</span><span style="color: #0000ff;">&gt;</span>
    <span style="color: #008000;">&lt;!--</span><span style="color: #008000;"> 导入服务网关zuul </span><span style="color: #008000;">--&gt;</span>
    <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">&gt;</span>
        <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">&gt;</span>org.springframework.cloud<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">&gt;</span>
        <span style="color: #0000ff;">&lt;</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">&gt;</span>spring-cloud-starter-netflix-zuul<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">&gt;</span>
    <span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">&gt;</span>
<span style="color: #0000ff;">&lt;/</span><span style="color: #800000;">dependencies</span><span style="color: #0000ff;">&gt;</span></pre> 

  以上就是我们整个demo的全部maven依赖了,很简洁吧。这也是springboot的初衷,把所有的工作都移到幕后,让业务更简洁。

 

1.2. 编写网关入口类

  如下为整个网关的入口类,实际上就是两个注解发生了化学反应。@EnableZuulProxy 是本文的主角,它会开启网关相关的服务。

@SpringBootApplication
@EnableZuulProxy
public class MyZuulGateway {
    // 只有一个空的main方法
    public static void main(String[] args) {
        SpringApplication.run(MyZuulGateway.class, args);
    }
}

  就是这么简单!

 

1.3. 添加测试配置项

  在application.properties配置文件中添加如下配置,主要使用一个路由配置验证即可!

server.port=9000
spring.application.name=my-zuul-gateway

#本地环境配置zuul转发的规则:

忽略所有微服务,只路由指定微服务

如下配置为将 /sapi/** 的路径请求,转发到 http://127.0.0.1:8082/file-encrypt-service/ 上去。

zuul.ignored-services=* zuul.routes.fileenc1.url=http://127.0.0.1:8082/fileenc/ zuul.routes.fileenc1.path=/sapi/**

  如上就可以将网关跑起来了,如果你连后台服务也没有,没关系,自己写一个就好了。

    @GetMapping("hello")
    public Object hello() {
        return "hello, world";
    }

  

1.4. 测试网关

  以上就已经将整个网关搞好了,run一下就ok. 测试方式就是直接浏览器里访问下该网关地址就好了: http://localhost:9000/sapi/test/hello .

  如果你看到 “hello, world”, 恭喜你,zuul已入坑。

 

二、zuul是如何转发请求的?

  根据上面的观察,zuul已经基本可以满足我们的开发需求了,后续更多要做的可能就是一些安全相关,业务相关,优化相关的东西了。不过在做这些之前,我们可以先多问一个问题,zuul是如何将请求转发给后台服务的呢?

  这实际上和zuul的架构相关:

 

  zuul的中核心概念是:Filter. 运行时分为  PRE:这种过滤器在请求被路由之前调用;ROUTING:这种过滤器将请求路由到微服务;POST:这种过滤器在路由到微服务以后执行;ERROR:在其他阶段发生错误时执行该过滤器;

  所以,整体上来说,它的转发流程会经过一系列的过滤器,然后再进行实际的转发。如果只想了解其最终是如何转的可以直奔主题,而如果要添加你的功能,则需要编写一些前置的过滤器。

  原本要分析zuul是如何处理请求的,但是实际上,zuul被整合到spring之后,就完全地符合了一个springmvc的编程模型了。所有对该网关的请求会先调用 ZuulController 进行请求的接收,然后到 service处理,再到响应这么一个过程。

  整个 ZuulController 非常地简单:就是一个请求的委托过程!

// org.springframework.cloud.netflix.zuul.web.ZuulController
public class ZuulController extends ServletWrappingController {
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> ZuulController() {
    setServletClass(ZuulServlet.</span><span style="color: #0000ff;">class</span><span style="color: #000000;">);
    setServletName(</span>"zuul"<span style="color: #000000;">);
    setSupportedMethods((String[]) </span><span style="color: #0000ff;">null</span>); <span style="color: #008000;">//</span><span style="color: #008000;"> Allow all</span>

}

@Override
</span><span style="color: #0000ff;">public</span> ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> Exception {
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> We don't care about the other features of the base class, just want to
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> handle the request</span>
        <span style="color: #0000ff;">return</span> <span style="color: #0000ff;">super</span><span style="color: #000000;">.handleRequestInternal(request, response);
    }
    </span><span style="color: #0000ff;">finally</span><span style="color: #000000;"> {
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter</span>

RequestContext.getCurrentContext().unset(); } }

} // org.springframework.web.servlet.mvc.ServletWrappingController#handleRequestInternal /** * Invoke the wrapped Servlet instance. * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) */ @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {

    Assert.state(</span><span style="color: #0000ff;">this</span>.servletInstance != <span style="color: #0000ff;">null</span>, "No Servlet instance"<span style="color: #000000;">);
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 该 servletInstance 是 ZuulServlet, 整个zuul的实现框架由其控制</span>
    <span style="color: #0000ff;">this</span><span style="color: #000000;">.servletInstance.service(request, response);
    </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">null</span><span style="color: #000000;">;
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> com.netflix.zuul.http.ZuulServlet#service</span>

@Override public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try { // 初始化请求,由 zuulRunner 处理 init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

        </span><span style="color: #008000;">//</span><span style="color: #008000;"> Marks this request as having passed through the "Zuul engine", as opposed to servlets
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> explicitly bound in web.xml, for which requests will not have the same data attached
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> setZuulEngineRan 会旋转一个标识: "zuulEngineRan", true</span>
        RequestContext context =<span style="color: #000000;"> RequestContext.getCurrentContext();
        context.setZuulEngineRan();

        </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
            </span><span style="color: #008000;">//</span><span style="color: #008000;"> 前置过滤器</span>

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

    } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Throwable e) {
        error(</span><span style="color: #0000ff;">new</span> ZuulException(e, 500, "UNHANDLED_EXCEPTION_" +<span style="color: #000000;"> e.getClass().getName()));
    } </span><span style="color: #0000ff;">finally</span><span style="color: #000000;"> {
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 重置上下文,以备下次使用</span>

RequestContext.getCurrentContext().unset(); } }

  以上就是整个zuul对于普通请求的处理框架部分了。逻辑还是比较清晰的,简单的,前置+转发+后置处理。我们就几个重点部分说明一下:

 

2.1. 请求初始化

  该部分主要是将外部请求,接入到 zuul 的处理流程上,当然下面的实现主要是使用了 ThreadLocal 实现了上下文的衔接。

    // com.netflix.zuul.http.ZuulServlet#init
    /**
     * initializes request
     *
     * @param servletRequest
     * @param servletResponse
     */
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        zuulRunner.init(servletRequest, servletResponse);
    }
    // com.netflix.zuul.ZuulRunner#init
    /**
     * sets HttpServlet request and HttpResponse
     *
     * @param servletRequest
     * @param servletResponse
     */
    public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        // RequestContext 使用 ThreadLocal 进行保存,且保证有值
        // 且 RequestContext 继承了 ConcurrentHashMap, 保证了操作的线程安全
        RequestContext ctx = RequestContext.getCurrentContext();
        if (bufferRequests) {
            ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
        } else {
            ctx.setRequest(servletRequest);
        }
    ctx.setResponse(</span><span style="color: #0000ff;">new</span><span style="color: #000000;"> HttpServletResponseWrapper(servletResponse));
}</span></pre> 

  以上就是一个 zuul 请求的初始化了,简单地说就是设置好请求上下文,备用。

 

2.2. 前置处理过滤器

  前置处理过滤器主要用于标记一些请求类型,权限验证,安全过滤等等。是不可或缺一环。具体实现自行处理!我们来看一个整体的通用流程:

    // com.netflix.zuul.http.ZuulServlet#preRoute
    /**
     * executes "pre" filters
     *
     * @throws ZuulException
     */
    void preRoute() throws ZuulException {
        zuulRunner.preRoute();
    }
    // com.netflix.zuul.ZuulRunner#preRoute
    /**
     * executes "pre" filterType  ZuulFilters
     *
     * @throws ZuulException
     */
    public void preRoute() throws ZuulException {
        // FilterProcessor 是个单例
        FilterProcessor.getInstance().preRoute();
    }
    // com.netflix.zuul.FilterProcessor#preRoute
    /**
     * runs all "pre" filters. These filters are run before routing to the orgin.
     *
     * @throws ZuulException
     */
    public void preRoute() throws ZuulException {
        try {
            // 调用Type 为 pre 的过滤器
            runFilters("pre");
        } catch (ZuulException e) {
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
        }
    }
    // com.netflix.zuul.FilterProcessor#runFilters
    /**
     * runs all filters of the filterType sType/ Use this method within filters to run custom filters by type
     *
     * @param sType the filterType.
     * @return
     * @throws Throwable throws up an arbitrary exception
     */
    public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }
        boolean bResult = false;
        // 通过 FilterLoader 的单例,获取所有注册为 sType 的过滤器
        // 存放 Filters 的容器自然也是线程安全的,为 ConcurrentHashMap
        // - org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter
        // - org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter
        // - org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter
        // - org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter
        // - org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                // 依次处理每个 filter
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }
    // 获取相应的 filters
    // com.netflix.zuul.FilterLoader#getFiltersByType
    /**
     * Returns a list of filters by the filterType specified
     *
     * @param filterType
     * @return a List<ZuulFilter>
     */
    public List<ZuulFilter> getFiltersByType(String filterType) {
    List</span>&lt;ZuulFilter&gt; list =<span style="color: #000000;"> hashFiltersByType.get(filterType);
    </span><span style="color: #0000ff;">if</span> (list != <span style="color: #0000ff;">null</span>) <span style="color: #0000ff;">return</span><span style="color: #000000;"> list;

    list </span>= <span style="color: #0000ff;">new</span> ArrayList&lt;ZuulFilter&gt;<span style="color: #000000;">();

    Collection</span>&lt;ZuulFilter&gt; filters =<span style="color: #000000;"> filterRegistry.getAllFilters();
    </span><span style="color: #0000ff;">for</span> (Iterator&lt;ZuulFilter&gt; iterator =<span style="color: #000000;"> filters.iterator(); iterator.hasNext(); ) {
        ZuulFilter filter </span>=<span style="color: #000000;"> iterator.next();
        </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (filter.filterType().equals(filterType)) {
            list.add(filter);
        }
    }
    Collections.sort(list); </span><span style="color: #008000;">//</span><span style="color: #008000;"> sort by priority</span>
hashFiltersByType.putIfAbsent(filterType, list); return list; }
</span><span style="color: #008000;">//</span><span style="color: #008000;"> com.netflix.zuul.FilterProcessor#processZuulFilter</span>
<span style="color: #008000;">/**</span><span style="color: #008000;">
 * Processes an individual ZuulFilter. This method adds Debug information. Any uncaught Thowables are caught by this method and converted to a ZuulException with a 500 status code.
 *
 * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> filter
 * </span><span style="color: #808080;">@return</span><span style="color: #008000;"> the return value for that filter
 * </span><span style="color: #808080;">@throws</span><span style="color: #008000;"> ZuulException
 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span> Object processZuulFilter(ZuulFilter filter) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> ZuulException {

    RequestContext ctx </span>=<span style="color: #000000;"> RequestContext.getCurrentContext();
    </span><span style="color: #0000ff;">boolean</span> bDebug =<span style="color: #000000;"> ctx.debugRouting();
    </span><span style="color: #0000ff;">final</span> String metricPrefix = "zuul.filter-"<span style="color: #000000;">;
    </span><span style="color: #0000ff;">long</span> execTime = 0<span style="color: #000000;">;
    String filterName </span>= ""<span style="color: #000000;">;
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
        </span><span style="color: #0000ff;">long</span> ltime =<span style="color: #000000;"> System.currentTimeMillis();
        filterName </span>=<span style="color: #000000;"> filter.getClass().getSimpleName();
        
        RequestContext copy </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;
        Object o </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;
        Throwable t </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;

        </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (bDebug) {
            Debug.addRoutingDebug(</span>"Filter " + filter.filterType() + " " + filter.filterOrder() + " " +<span style="color: #000000;"> filterName);
            copy </span>=<span style="color: #000000;"> ctx.copy();
        }
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 调用各filter的 runFilter() 方法,触发filter作用
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 如果filter被禁用,则不会调用 zuul.ServletDetectionFilter.pre.disable=true, 代表禁用 pre
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 具体实现逻辑由各 filter 决定 </span>
        ZuulFilterResult result =<span style="color: #000000;"> filter.runFilter();
        ExecutionStatus s </span>=<span style="color: #000000;"> result.getStatus();
        execTime </span>= System.currentTimeMillis() -<span style="color: #000000;"> ltime;

        </span><span style="color: #0000ff;">switch</span><span style="color: #000000;"> (s) {
            </span><span style="color: #0000ff;">case</span><span style="color: #000000;"> FAILED:
                t </span>=<span style="color: #000000;"> result.getException();
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
            </span><span style="color: #0000ff;">case</span><span style="color: #000000;"> SUCCESS:
                o </span>=<span style="color: #000000;"> result.getResult();
                </span><span style="color: #008000;">//</span><span style="color: #008000;"> 使用 StringBuilder 记录请求处理日志</span>

ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime); if (bDebug) { Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms"); Debug.compareContextState(filterName, copy); } break; default: break; } // 只要发生异常,则抛出 if (t != null) throw t; // 请求计数器增加 usageNotifier.notify(filter, s); return o;

    } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Throwable e) {
        </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (bDebug) {
            Debug.addRoutingDebug(</span>"Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " +<span style="color: #000000;"> e.getMessage());
        }
        usageNotifier.notify(filter, ExecutionStatus.FAILED);
        </span><span style="color: #0000ff;">if</span> (e <span style="color: #0000ff;">instanceof</span><span style="color: #000000;"> ZuulException) {
            </span><span style="color: #0000ff;">throw</span><span style="color: #000000;"> (ZuulException) e;
        } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
            ZuulException ex </span>= <span style="color: #0000ff;">new</span> ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" +<span style="color: #000000;"> filterName);
            ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
            </span><span style="color: #0000ff;">throw</span><span style="color: #000000;"> ex;
        }
    }
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> com.netflix.zuul.ZuulFilter#runFilter</span>
<span style="color: #008000;">/**</span><span style="color: #008000;">
 * runFilter checks !isFilterDisabled() and shouldFilter(). The run() method is invoked if both are true.
 *
 * </span><span style="color: #808080;">@return</span><span style="color: #008000;"> the return from ZuulFilterResult
 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span><span style="color: #000000;"> ZuulFilterResult runFilter() {
    ZuulFilterResult zr </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> ZuulFilterResult();
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 如果被禁用则不会触发真正地调用</span>
    <span style="color: #0000ff;">if</span> (!<span style="color: #000000;">isFilterDisabled()) {
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> shouldFilter() 由各filter决定,返回true时执行filter</span>
        <span style="color: #0000ff;">if</span><span style="color: #000000;"> (shouldFilter()) {
            Tracer t </span>= TracerFactory.instance().startMicroTracer("ZUUL::" + <span style="color: #0000ff;">this</span><span style="color: #000000;">.getClass().getSimpleName());
            </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
                Object res </span>=<span style="color: #000000;"> run();
                zr </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> ZuulFilterResult(res, ExecutionStatus.SUCCESS);
            } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Throwable e) {
                t.setName(</span>"ZUUL::" + <span style="color: #0000ff;">this</span>.getClass().getSimpleName() + " failed"<span style="color: #000000;">);
                zr </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> ZuulFilterResult(ExecutionStatus.FAILED);
                zr.setException(e);
            } </span><span style="color: #0000ff;">finally</span><span style="color: #000000;"> {
                t.stopAndLog();
            }
        } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
            </span><span style="color: #008000;">//</span><span style="color: #008000;"> 打上跳过标识</span>
            zr = <span style="color: #0000ff;">new</span><span style="color: #000000;"> ZuulFilterResult(ExecutionStatus.SKIPPED);
        }
    }
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> zr;
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter#run</span>

@Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); 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); }

    </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">null</span><span style="color: #000000;">;
}</span></pre> 

  如上,就是一个preFilter的处理流程了:

    1. 从 FilterLoader 中获取所有 pre 类型的filter;
    2. 依次调用各filter的runFilter()方法,触发filter;
    3. 调用前先调用 shouldFilter() 进行判断该filter对于此次请求是否有用, 各filter实现可以从上下文中取得相应的信息,各自判定;
    4. 计数器加1;
    5. 默认就会有多个filter可调用, 不够满足业务场景再自行添加;

 

2.3. 正常路由处理

  zuul 的本职工作,是对路径的转发路由(正向代理 or 反向代理),如下处理:

    // com.netflix.zuul.http.ZuulServlet#route
    /**
     * executes "route" filters
     *
     * @throws ZuulException
     */
    void route() throws ZuulException {
        zuulRunner.route();
    }
    // com.netflix.zuul.ZuulRunner#route
    /**
     * executes "route" filterType  ZuulFilters
     *
     * @throws ZuulException
     */
    public void route() throws ZuulException {
        FilterProcessor.getInstance().route();
    }
    // com.netflix.zuul.FilterProcessor#route
    /**
     * Runs all "route" filters. These filters route calls to an origin.
     *
     * @throws ZuulException if an exception occurs.
     */
    public void route() throws ZuulException {
        try {
            // 同样,获取filter类型为 route 的 filters, 进行调用处理即可
            // - org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter
            // - org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter
            // - org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter
            runFilters("route");
        } catch (ZuulException e) {
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
        }
    }
    // 其中,Ribbon 的处理需要有 ribbon 组件的引入和配置
    // org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter#shouldFilter
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        // 判断是否有 serviceId, 且 sendZuulResponse=true 才会进行 ribbon 处理
        return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
                && ctx.sendZuulResponse());
    }
以下是普通路由转发的实现,只要配置了相应的路由信息,则会进行相关转发:
</span><span style="color: #008000;">//</span><span style="color: #008000;"> org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#shouldFilter</span>

@Override public boolean shouldFilter() { return RequestContext.getCurrentContext().getRouteHost() != null && RequestContext.getCurrentContext().sendZuulResponse(); }

@Override
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> Object run() {
    RequestContext context </span>=<span style="color: #000000;"> RequestContext.getCurrentContext();
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> step1. 构建http请求头信息</span>
    HttpServletRequest request =<span style="color: #000000;"> context.getRequest();
    MultiValueMap</span>&lt;String, String&gt; headers = <span style="color: #0000ff;">this</span><span style="color: #000000;">.helper
            .buildZuulRequestHeaders(request);
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> step2. 构建 params 信息, 如: a=111&amp;&amp;b=222</span>
    MultiValueMap&lt;String, String&gt; params = <span style="color: #0000ff;">this</span><span style="color: #000000;">.helper
            .buildZuulRequestQueryParams(request);
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 获取请求类型, GET,POST,PUT,DELETE</span>
    String verb =<span style="color: #000000;"> getVerb(request);
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> step3. 构建请求体信息,如文件</span>
    InputStream requestEntity =<span style="color: #000000;"> getRequestBody(request);
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 如果没有 Content-Length 字段,则设置 chunkedRequestBody:true</span>
    <span style="color: #0000ff;">if</span> (getContentLength(request) &lt; 0<span style="color: #000000;">) {
        context.setChunkedRequestBody();
    }
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> step4. 构建要转发的uri地址信息</span>
    String uri = <span style="color: #0000ff;">this</span><span style="color: #000000;">.helper.buildZuulRequestURI(request);
    </span><span style="color: #0000ff;">this</span><span style="color: #000000;">.helper.addIgnoredHeaders();

    </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> step5. 请求转发出去,等待响应
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 具体如何转发请求,是在 forward 中处理的</span>
        CloseableHttpResponse response = forward(<span style="color: #0000ff;">this</span><span style="color: #000000;">.httpClient, verb, uri, request,
                headers, params, requestEntity);
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 将结果放到上下文中,以备后续filter处理</span>

setResponse(response); } catch (Exception ex) { throw new ZuulRuntimeException(ex); } return null; }

</span><span style="color: #008000;">//</span><span style="color: #008000;"> step1. 构建http请求头信息
</span><span style="color: #008000;">//</span><span style="color: #008000;"> org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper#buildZuulRequestHeaders</span>
<span style="color: #0000ff;">public</span> MultiValueMap&lt;String, String&gt;<span style="color: #000000;"> buildZuulRequestHeaders(
        HttpServletRequest request) {
    RequestContext context </span>=<span style="color: #000000;"> RequestContext.getCurrentContext();
    MultiValueMap</span>&lt;String, String&gt; headers = <span style="color: #0000ff;">new</span><span style="color: #000000;"> HttpHeaders();
    Enumeration</span>&lt;String&gt; headerNames =<span style="color: #000000;"> request.getHeaderNames();
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 获取所有的 header 信息,还原到 headers 中</span>
    <span style="color: #0000ff;">if</span> (headerNames != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
        </span><span style="color: #0000ff;">while</span><span style="color: #000000;"> (headerNames.hasMoreElements()) {
            String name </span>=<span style="color: #000000;"> headerNames.nextElement();
            </span><span style="color: #008000;">//</span><span style="color: #008000;"> 排除一些特别的的头信息</span>
            <span style="color: #0000ff;">if</span><span style="color: #000000;"> (isIncludedHeader(name)) {
                Enumeration</span>&lt;String&gt; values =<span style="color: #000000;"> request.getHeaders(name);
                </span><span style="color: #0000ff;">while</span><span style="color: #000000;"> (values.hasMoreElements()) {
                    String value </span>=<span style="color: #000000;"> values.nextElement();
                    headers.add(name, value);
                }
            }
        }
    }
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 添加本次路由转发新增的头信息</span>
    Map&lt;String, String&gt; zuulRequestHeaders =<span style="color: #000000;"> context.getZuulRequestHeaders();
    </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (String header : zuulRequestHeaders.keySet()) {
        headers.set(header, zuulRequestHeaders.get(header));
    }
    headers.set(HttpHeaders.ACCEPT_ENCODING, </span>"gzip"<span style="color: #000000;">);
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> headers;
}

</span><span style="color: #008000;">//</span><span style="color: #008000;"> step2. 构建 params 信息, 如: a=111&amp;&amp;b=222
</span><span style="color: #008000;">//</span><span style="color: #008000;"> org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper#buildZuulRequestQueryParams</span>
<span style="color: #0000ff;">public</span> MultiValueMap&lt;String, String&gt;<span style="color: #000000;"> buildZuulRequestQueryParams(
        HttpServletRequest request) {
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 解析 getQueryString 中的 a=111&amp;b=222... 信息</span>
    Map&lt;String, List&lt;String&gt;&gt; map =<span style="color: #000000;"> HTTPRequestUtils.getInstance().getQueryParams();
    MultiValueMap</span>&lt;String, String&gt; params = <span style="color: #0000ff;">new</span> LinkedMultiValueMap&lt;&gt;<span style="color: #000000;">();
    </span><span style="color: #0000ff;">if</span> (map == <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> params;
    }
    </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (String key : map.keySet()) {
        </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (String value : map.get(key)) {
            params.add(key, value);
        }
    }
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> params;
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 解析请求url中的k=v&amp;k2=v2 为 map 格式
</span><span style="color: #008000;">//</span><span style="color: #008000;"> com.netflix.zuul.util.HTTPRequestUtils#getQueryParams</span>
<span style="color: #008000;">/**</span><span style="color: #008000;">
 * returns query params as a Map with String keys and Lists of Strings as values
 * </span><span style="color: #808080;">@return</span>
 <span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span> Map&lt;String, List&lt;String&gt;&gt;<span style="color: #000000;"> getQueryParams() {

    Map</span>&lt;String, List&lt;String&gt;&gt; qp =<span style="color: #000000;"> RequestContext.getCurrentContext().getRequestQueryParams();
    </span><span style="color: #0000ff;">if</span> (qp != <span style="color: #0000ff;">null</span>) <span style="color: #0000ff;">return</span><span style="color: #000000;"> qp;

    HttpServletRequest request </span>=<span style="color: #000000;"> RequestContext.getCurrentContext().getRequest();

    qp </span>= <span style="color: #0000ff;">new</span> LinkedHashMap&lt;String, List&lt;String&gt;&gt;<span style="color: #000000;">();

    </span><span style="color: #0000ff;">if</span> (request.getQueryString() == <span style="color: #0000ff;">null</span>) <span style="color: #0000ff;">return</span> <span style="color: #0000ff;">null</span><span style="color: #000000;">;
    StringTokenizer st </span>= <span style="color: #0000ff;">new</span> StringTokenizer(request.getQueryString(), "&amp;"<span style="color: #000000;">);
    </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> i;

    </span><span style="color: #0000ff;">while</span><span style="color: #000000;"> (st.hasMoreTokens()) {
        String s </span>=<span style="color: #000000;"> st.nextToken();
        i </span>= s.indexOf("="<span style="color: #000000;">);
        </span><span style="color: #0000ff;">if</span> (i &gt; 0 &amp;&amp; s.length() &gt;= i + 1<span style="color: #000000;">) {
            String name </span>= s.substring(0<span style="color: #000000;">, i);
            String value </span>= s.substring(i + 1<span style="color: #000000;">);

            </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
                name </span>= URLDecoder.decode(name, "UTF-8"<span style="color: #000000;">);
            } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e) {
            }
            </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
                value </span>= URLDecoder.decode(value, "UTF-8"<span style="color: #000000;">);
            } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e) {
            }

            List</span>&lt;String&gt; valueList =<span style="color: #000000;"> qp.get(name);
            </span><span style="color: #0000ff;">if</span> (valueList == <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
                valueList </span>= <span style="color: #0000ff;">new</span> LinkedList&lt;String&gt;<span style="color: #000000;">();
                qp.put(name, valueList);
            }

            valueList.add(value);
        }
        </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (i == -1<span style="color: #000000;">)
        {
            String name</span>=<span style="color: #000000;">s;
            String value</span>=""<span style="color: #000000;">;
            </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
                name </span>= URLDecoder.decode(name, "UTF-8"<span style="color: #000000;">);
            } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e) {
            }
           
            List</span>&lt;String&gt; valueList =<span style="color: #000000;"> qp.get(name);
            </span><span style="color: #0000ff;">if</span> (valueList == <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
                valueList </span>= <span style="color: #0000ff;">new</span> LinkedList&lt;String&gt;<span style="color: #000000;">();
                qp.put(name, valueList);
            }

            valueList.add(value);
            
        }
    }

    RequestContext.getCurrentContext().setRequestQueryParams(qp);
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> qp;
}


</span><span style="color: #008000;">//</span><span style="color: #008000;"> step3. 构建请求体信息,如文件
</span><span style="color: #008000;">//</span><span style="color: #008000;"> org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#getRequestBody</span>
<span style="color: #0000ff;">protected</span><span style="color: #000000;"> InputStream getRequestBody(HttpServletRequest request) {
    InputStream requestEntity </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 先向 requestEntity 中获取输入流,如果没有则向 servlet 中获取</span>
        requestEntity =<span style="color: #000000;"> (InputStream) RequestContext.getCurrentContext().get(REQUEST_ENTITY_KEY);
        </span><span style="color: #0000ff;">if</span> (requestEntity == <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
            </span><span style="color: #008000;">//</span><span style="color: #008000;"> 向 HttpServletRequest 中获取原始的输入流</span>
            requestEntity =<span style="color: #000000;"> request.getInputStream();
        }
    }
    </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (IOException ex) {
        log.error(</span>"error during getRequestBody"<span style="color: #000000;">, ex);
    }
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> requestEntity;
}


</span><span style="color: #008000;">//</span><span style="color: #008000;"> step4. 构建要转发的uri地址信息
</span><span style="color: #008000;">//</span><span style="color: #008000;"> org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper#buildZuulRequestURI</span>
<span style="color: #0000ff;">public</span><span style="color: #000000;"> String buildZuulRequestURI(HttpServletRequest request) {
    RequestContext context </span>=<span style="color: #000000;"> RequestContext.getCurrentContext();
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 原始请求 uri</span>
    String uri =<span style="color: #000000;"> request.getRequestURI();
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 路由转换之后的请求 uri</span>
    String contextURI =<span style="color: #000000;"> (String) context.get(REQUEST_URI_KEY);
    </span><span style="color: #0000ff;">if</span> (contextURI != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
        </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
            </span><span style="color: #008000;">//</span><span style="color: #008000;"> 防止乱码,urlencode 一下</span>
            uri =<span style="color: #000000;"> UriUtils.encodePath(contextURI, characterEncoding(request));
        }
        </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e) {
            log.debug(
                    </span>"unable to encode uri path from context, falling back to uri from request"<span style="color: #000000;">,
                    e);
        }
    }
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> uri;
}

</span><span style="color: #008000;">//</span><span style="color: #008000;"> step5. 请求转发出去,等待响应
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 具体如何转发请求,是在 forward 中处理的
</span><span style="color: #008000;">//</span><span style="color: #008000;"> org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#forward</span>
<span style="color: #0000ff;">private</span><span style="color: #000000;"> CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb,
        String uri, HttpServletRequest request, MultiValueMap</span>&lt;String, String&gt;<span style="color: #000000;"> headers,
        MultiValueMap</span>&lt;String, String&gt;<span style="color: #000000;"> params, InputStream requestEntity)
        </span><span style="color: #0000ff;">throws</span><span style="color: #000000;"> Exception {
    Map</span>&lt;String, Object&gt; info = <span style="color: #0000ff;">this</span><span style="color: #000000;">.helper.debug(verb, uri, headers, params,
            requestEntity);
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 配置的路由地址前缀</span>
    URL host =<span style="color: #000000;"> RequestContext.getCurrentContext().getRouteHost();
    HttpHost httpHost </span>=<span style="color: #000000;"> getHttpHost(host);
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 取出uri</span>
    uri = StringUtils.cleanPath((host.getPath() + uri).replaceAll("/{2,}", "/"<span style="color: #000000;">));
    </span><span style="color: #0000ff;">long</span> contentLength =<span style="color: #000000;"> getContentLength(request);

    ContentType contentType </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;

    </span><span style="color: #0000ff;">if</span> (request.getContentType() != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
        contentType </span>=<span style="color: #000000;"> ContentType.parse(request.getContentType());
    }
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 使用InputStreamEntity封装inputStream请求,该inputStream是从socket接入后的原始输入流
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 后续 httpclient 进行数据读取时,将由其进行提供相应读数据方法</span>
    InputStreamEntity entity = <span style="color: #0000ff;">new</span><span style="color: #000000;"> InputStreamEntity(requestEntity, contentLength,
            contentType);
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 构建本次要请求的数据,关键</span>
    HttpRequest httpRequest =<span style="color: #000000;"> buildHttpRequest(verb, uri, entity, headers, params,
            request);
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
        log.debug(httpHost.getHostName() </span>+ " " + httpHost.getPort() + " "
                +<span style="color: #000000;"> httpHost.getSchemeName());
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 提交给 httpclient 组件执行 http 请求,并返回结果</span>
        CloseableHttpResponse zuulResponse =<span style="color: #000000;"> forwardRequest(httpclient, httpHost,
                httpRequest);
        </span><span style="color: #0000ff;">this</span><span style="color: #000000;">.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(),
                revertHeaders(zuulResponse.getAllHeaders()));
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> zuulResponse;
    }
    </span><span style="color: #0000ff;">finally</span><span style="color: #000000;"> {
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> When HttpClient instance is no longer needed,
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> shut down the connection manager to ensure
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> immediate deallocation of all system resources
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> httpclient.getConnectionManager().shutdown();</span>

} } // org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#buildHttpRequest 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)); // 根据原始请求的不同类型,做相应类型的转发 // 以下请求处理,都包含了对 文件流一类请求的逻辑 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; // DELETE 时会做两步操作 entityRequest.setEntity(entity); break; default: // 除以上几种情况,都使用 BasicHttpRequest 进行处理即可 httpRequest = new BasicHttpRequest(verb, uriWithQueryString); log.debug(uriWithQueryString); } // 统一都设置请求头,将map转换为 BasicHeader httpRequest.setHeaders(convertHeaders(headers)); return httpRequest; } // org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#forwardRequest private CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient, HttpHost httpHost, HttpRequest httpRequest) throws IOException { return httpclient.execute(httpHost, httpRequest); }

  可见整个真正的转发流程,主要分几步:

    1. 解析http请求头信息,并添加自己部分的头信息;
    2. 解析并保留请求参数信息, 如: a=111&&b=222;
    3. 获取原始的inputStream信息,如文件;
    4. 根据路由配置,构建要转发的uri地址信息;
    5. 使用httpclient组件,将请求转发出去,并等待响应,设置到 response中;

  实际上,真正的转发仍然是依次做好相应判断,然后还原成对应的请求,再转发后后端服务中。

  以上,就是一个普通的服务转发实现了。并没有太多的技巧,而是最基础的步骤:接收请求,解析参数,重新构建请求,请求后端,获得结果。

 

2.4. 后置过滤器

  后置处理器可以做一些请求完服务端之后,对客户端的响应数据,包括正常数据流的输出,错误信息的返回等。如 SendResponseFilter, SendErrorFilter...

    // com.netflix.zuul.http.ZuulServlet#postRoute
    /**
     * executes "post" ZuulFilters
     *
     * @throws ZuulException
     */
    void postRoute() throws ZuulException {
        zuulRunner.postRoute();
    }
</span><span style="color: #008000;">//</span><span style="color: #008000;"> com.netflix.zuul.ZuulRunner#postRoute</span>
<span style="color: #008000;">/**</span><span style="color: #008000;">
 * executes "post" filterType  ZuulFilters
 *
 * </span><span style="color: #808080;">@throws</span><span style="color: #008000;"> ZuulException
 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> postRoute() <span style="color: #0000ff;">throws</span><span style="color: #000000;"> ZuulException {
    FilterProcessor.getInstance().postRoute();
}

</span><span style="color: #008000;">//</span><span style="color: #008000;"> com.netflix.zuul.FilterProcessor#postRoute</span>
<span style="color: #008000;">/**</span><span style="color: #008000;">
 * runs "post" filters which are called after "route" filters. ZuulExceptions from ZuulFilters are thrown.
 * Any other Throwables are caught and a ZuulException is thrown out with a 500 status code
 *
 * </span><span style="color: #808080;">@throws</span><span style="color: #008000;"> ZuulException
 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> postRoute() <span style="color: #0000ff;">throws</span><span style="color: #000000;"> ZuulException {
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 获取类型为 post 的 filter, 调用
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 默认为: org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter</span>
        runFilters("post"<span style="color: #000000;">);
    } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (ZuulException e) {
        </span><span style="color: #0000ff;">throw</span><span style="color: #000000;"> e;
    } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Throwable e) {
        </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" +<span style="color: #000000;"> e.getClass().getName());
    }
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#shouldFilter</span>

@Override public boolean shouldFilter() { // 有响应的数据,就可以进行处理 RequestContext context = RequestContext.getCurrentContext(); return context.getThrowable() == null && (!context.getZuulResponseHeaders().isEmpty() || context.getResponseDataStream() != null || context.getResponseBody() != null); } // org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#run @Override public Object run() { try { // 添加header信息 addResponseHeaders(); // 输出数据流到请求端 writeResponse(); } catch (Exception ex) { ReflectionUtils.rethrowRuntimeException(ex); } return null; } // org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#addResponseHeaders private void addResponseHeaders() { RequestContext context = RequestContext.getCurrentContext(); HttpServletResponse servletResponse = context.getResponse(); if (this.zuulProperties.isIncludeDebugHeader()) { @SuppressWarnings("unchecked") List<String> rd = (List<String>) context.get(ROUTING_DEBUG_KEY); if (rd != null) { StringBuilder debugHeader = new StringBuilder(); for (String it : rd) { debugHeader.append("[[[" + it + "]]]"); } servletResponse.addHeader(X_ZUUL_DEBUG_HEADER, debugHeader.toString()); } } // 向 response 中添加header List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders(); if (zuulResponseHeaders != null) { for (Pair<String, String> it : zuulResponseHeaders) { servletResponse.addHeader(it.first(), it.second()); } } if (includeContentLengthHeader(context)) { Long contentLength = context.getOriginContentLength(); if(useServlet31) { servletResponse.setContentLengthLong(contentLength); } else { //Try and set some kind of content length if we can safely convert the Long to an int if (isLongSafe(contentLength)) { servletResponse.setContentLength(contentLength.intValue()); } } } } // org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#writeResponse() 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 </span>=<span style="color: #000000;"> servletResponse.getOutputStream();
    InputStream is </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
        </span><span style="color: #0000ff;">if</span> (context.getResponseBody() != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
            String body </span>=<span style="color: #000000;"> context.getResponseBody();
            is </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> ByteArrayInputStream(
                            body.getBytes(servletResponse.getCharacterEncoding()));
        }
        </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
            is </span>=<span style="color: #000000;"> context.getResponseDataStream();
            </span><span style="color: #0000ff;">if</span> (is!=<span style="color: #0000ff;">null</span> &amp;&amp;<span style="color: #000000;"> context.getResponseGZipped()) {
                </span><span style="color: #008000;">//</span><span style="color: #008000;"> if origin response is gzipped, and client has not requested gzip,
                </span><span style="color: #008000;">//</span><span style="color: #008000;"> decompress stream before sending to client
                </span><span style="color: #008000;">//</span><span style="color: #008000;"> else, stream gzip directly to client</span>
                <span style="color: #0000ff;">if</span><span style="color: #000000;"> (isGzipRequested(context)) {
                    servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING, </span>"gzip"<span style="color: #000000;">);
                }
                </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
                    is </span>=<span style="color: #000000;"> handleGzipStream(is);
                }
            }
        }
        
        </span><span style="color: #0000ff;">if</span> (is!=<span style="color: #0000ff;">null</span><span style="color: #000000;">) {
            writeResponse(is, outStream);
        }
    }
    </span><span style="color: #0000ff;">finally</span><span style="color: #000000;"> {
        </span><span style="color: #008000;">/**</span><span style="color: #008000;">
        * We must ensure that the InputStream provided by our upstream pooling mechanism is ALWAYS closed
        * even in the case of wrapped streams, which are supplied by pooled sources such as Apache's
        * PoolingHttpClientConnectionManager. In that particular case, the underlying HTTP connection will
        * be returned back to the connection pool iif either close() is explicitly called, a read
        * error occurs, or the end of the underlying stream is reached. If, however a write error occurs, we will
        * end up leaking a connection from the pool without an explicit close()
        *
        * </span><span style="color: #808080;">@author</span><span style="color: #008000;"> Johannes Edmeier
        </span><span style="color: #008000;">*/</span>
        <span style="color: #0000ff;">if</span> (is != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
            </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
                is.close();
            }
            </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception ex) {
                log.warn(</span>"Error while closing upstream input stream"<span style="color: #000000;">, ex);
            }
        }

        </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
            Object zuulResponse </span>= context.get("zuulResponse"<span style="color: #000000;">);
            </span><span style="color: #0000ff;">if</span> (zuulResponse <span style="color: #0000ff;">instanceof</span><span style="color: #000000;"> Closeable) {
                ((Closeable) zuulResponse).close();
            }
            outStream.flush();
            </span><span style="color: #008000;">//</span><span style="color: #008000;"> The container will close the stream for us</span>

} catch (IOException ex) { log.warn("Error while sending response to client: " + ex.getMessage()); } } } // org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#writeResponse private void writeResponse(InputStream zin, OutputStream out) throws Exception { // 默认大小 8192 byte[] bytes = buffers.get(); int bytesRead = -1; // 依次向 outputStream 中写入字节流 while ((bytesRead = zin.read(bytes)) != -1) { out.write(bytes, 0, bytesRead); } }

  同样,对客户端的输出,就是这么简单:解析出header信息,将response write() 到客户端的socket中。即完成任务。

  以上,我们主要看了几个非常普通的filter的处理过程,理解了下 zuul 的运行流程,当然主要的目的分析zuul是如何转发请求的。基本上上面所有的filter都会继承 ZuulFilter 的抽象,它提供两个重要的统一的方法:isFilterDisabled() 和 shouldFilter() 方法用于控制过虑器是否启用或者是否应该使用,并统一了返回结果。

 

   zuul 整体实现也是非常简单明了,基于模板方法模式 和 责任链模式 和 单例模式,基本搞定。只是更多的花需要应用自己去玩了。

 

三、自行实现一个业务filter

  要想做到通用的框架,这点事情是必须要做的。当然,还必须要足够简单,如下:一个注解加一个继承实现即可!

// 一个注解,@Component, 成功 spring bean 组件
// 一个继承,ZuulFilter, 使用 zuul 可以按照规范进行filter 的接入
@Component
public class MyOneFilter extends ZuulFilter {
</span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">final</span> UrlPathHelper urlPathHelper = <span style="color: #0000ff;">new</span><span style="color: #000000;"> UrlPathHelper();

@Autowired
</span><span style="color: #0000ff;">private</span><span style="color: #000000;"> ZuulProperties zuulProperties;

@Autowired
</span><span style="color: #0000ff;">private</span><span style="color: #000000;"> RouteLocator routeLocator;

</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> MyOneFilter() {
}

</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> MyOneFilter(ZuulProperties zuulProperties,
                   RouteLocator routeLocator) {
    </span><span style="color: #0000ff;">this</span>.routeLocator =<span style="color: #000000;"> routeLocator;
    </span><span style="color: #0000ff;">this</span>.zuulProperties =<span style="color: #000000;"> zuulProperties;
}

@Override
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> String filterType() {
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 自定义过滤器的类型,知道为什么不用枚举类吗?嘿嘿</span>
    <span style="color: #0000ff;">return</span><span style="color: #000000;"> PRE_TYPE;
}

@Override
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">int</span><span style="color: #000000;"> filterOrder() {
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 定义过滤器的出场顺序,越小越牛</span>
    <span style="color: #0000ff;">return</span> 1<span style="color: #000000;">;
}

@Override
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">boolean</span><span style="color: #000000;"> shouldFilter() {
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 是否可以启用当前filter, 按你的业务规则来说了算</span>
    <span style="color: #0000ff;">return</span> <span style="color: #0000ff;">true</span><span style="color: #000000;">;
}

@Override
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> Object run() {
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 如果满足了过滤条件,你想怎么做都行,RequestContext中有你想要的一切</span>
    RequestContext ctx =<span style="color: #000000;"> RequestContext.getCurrentContext();
    Route route </span>=<span style="color: #000000;"> routeLocator.getMatchingRoute(
            urlPathHelper.getPathWithinApplication(ctx.getRequest()));
    System.out.println(</span>"in my one filter"<span style="color: #000000;">);
    </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">null</span><span style="color: #000000;">;
}

}

  至于其他配置项什么的,自行查看官网即可! https://www.springcloud.cc/spring-cloud-greenwich.html#_router_and_filter_zuul

本文转载自博客园,原文链接:https://www.cnblogs.com/yougewe/p/13062471.html

全部评论: 0

    我有话说: