zuul自定义route源码分析

liwenbocsu 2019-06-06

总括:

1,自定义zuulfilter 纳入bean中管理即可

2,自定义路由ZuulProxyAutoConfiguration中RouteLocator实例化为自定义即可

3,zuul的控制层也是用servlet-controller,这调用连会调用过滤器(先),定义好的路由(后)----调用过程

ZuulFilter定义

通过继承ZuulFilter我们可以定义一个新的过滤器,如下

public class IpAddressFilter extends ZuulFilter {
    @Autowired
    private IGatewayService iGatewayService;

    @Override
    public String filterType() {
        // pre类型的过滤器
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 排序
        return 1;
    }

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

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        String ip = ctx.getRequest().getRemoteAddr();
        Set<String> blackList = ConcurrentCache.getBlackSet();
        Set<String> whiteList = ConcurrentCache.getWhiteSet();

        blackList.removeAll(whiteList);

        // 在黑名单中禁用
        if (StringUtils.isNotBlank(ip)&& blackList.contains(ip)) {
            ctx.setSendZuulResponse(false);

            ctx.setResponseBody("Suspected flooding attack, IP blocked");
            ctx.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
            ctx.addZuulResponseHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
            return null;
        }
        return null;
    }
}

ZuulFilter中实现了compareTo()方法,根据它的值决定同类型的filter的执行顺序。compareTo()方法如下:

public int compareTo(ZuulFilter filter) {
    return Integer.compare(this.filterOrder(), filter.filterOrder());
}

注册ZuulFilter到spring容器中

ZuulFilter可以通过@Component,也可以通过@Bean实例化来纳入spring的生命周期中。

@Configuration
public class FilterConfig {

    @Bean
    public IpAddressFilter addIpAddressFilter() {
        return new IpAddressFilter();
    }
}

ZuulServerAutoConfiguration中自动装配了filter,被spring实例化出来的所有的ZuulFilter都会被自动装配到Map中。

@Configuration
protected static class ZuulFilterConfiguration {
    // 根据类型,自动装配ZuulFilter到Map对象中
    @Autowired
    private Map<String, ZuulFilter> filters;

    @Bean
    public ZuulFilterInitializer zuulFilterInitializer(
            CounterFactory counterFactory, TracerFactory tracerFactory) {
        FilterLoader filterLoader = FilterLoader.getInstance();
        // 单例模式
        FilterRegistry filterRegistry = FilterRegistry.instance();
        return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
    }

}

上面的代码会调用ZuulFilterInitializer的构造方法。

ZuulFilterInitializer中的contextInitialized()开启了@PostConstruct注解,在构造方法完成时,容器会调用contextInitialized()方法(注意:ZuulFilterInitializer对象要由spring管理才会调用到@PostConstruct),将所有的filter保存到filterRegistry中,filterRegistry是一个单例对象。

说明:PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上

contextInitialized()方法如下:

@PostConstruct
public void contextInitialized() {
    log.info("Starting filter initializer");

    TracerFactory.initialize(tracerFactory);
    CounterFactory.initialize(counterFactory);

    for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
        // 保存filter
        filterRegistry.put(entry.getKey(), entry.getValue());
    }
}

自定义路由转发规则

ZuulProxyAutoConfiguration类中注册了RouteLocatorbean@Bean会按照类型,自动注入RouteLocator的实现类。

@Bean
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
        ProxyRequestHelper proxyRequestHelper) {
    return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(),
            this.zuulProperties, proxyRequestHelper);
}

RouteLocator实例化

@Configuration
public class AppConfig{
    //.....省略....
    @Bean(value = "discoveryRouteLocator")
    public DiscoveryClientRouteLocator discoveryClientRouteLocator(ServerProperties server, DiscoveryClient discovery, ZuulProperties properties,ServiceInstance localInstance) {
        return new CustomRouteLocator(server.getServletPath(), discovery,properties,localInstance);
    }

}

CustomRouteLocator实现自定义路由的功能,类如下。

public class CustomRouteLocator extends DiscoveryClientRouteLocator {
    // ....省略....

    @Override
    // 重写
    public Route getMatchingRoute(String path) {
        // ....省略....
        //可以从数据库中读取路由规则,并进行处理
    }

    // 重写
    @Override
    protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
        // ....省略....
    }
}

Servlet初始化

为什么通过访问网关可以自动跳转到zuul中,其实是通过servlet的实现的,该servlet对根路径/进行过滤。下面说明servlet的初始化内容。

ZuulServerAutoConfiguration类中定义了ZuulController

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

ZuulController继承了ServletWrappingController

public class ZuulController extends ServletWrappingController {

    public ZuulController() {
        // 设置类为ZuulServlet
        setServletClass(ZuulServlet.class);
        setServletName("zuul");
        setSupportedMethods((String[]) null);
    }

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            return super.handleRequestInternal(request, response);
        }
        finally {
            RequestContext.getCurrentContext().unset();
        }
    }

}

ServletWrappingControllerZuulServlet进行实例化

@Override
public void afterPropertiesSet() throws Exception {
    if (this.servletClass == null) {
        throw new IllegalArgumentException("'servletClass' is required");
    }
    if (this.servletName == null) {
        this.servletName = this.beanName;
    }
    // 实例化
    this.servletInstance = this.servletClass.newInstance();
    // 调用servlet的init方法
    this.servletInstance.init(new DelegatingServletConfig());
}

当访问一个url的时候,服务请求会跳转到ZuulController中,执行handleRequest()方法。

@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    try {
        // 调用父类的handleRequestInternal方法
        return super.handleRequestInternal(request, response);
    }
    finally {
        RequestContext.getCurrentContext().unset();
    }
}

handleRequestInternal()方法如下:

protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
        throws Exception {

    this.servletInstance.service(request, response);
    return null;
}

servletInstanceZuulServlet的实例,上面的方法最终调用ZuulServlet中的service()方法。

service()方法如下:

@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
    try {
        init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();

        try {
            // pre过滤器
            preRoute();
        } catch (ZuulException e) {
            error(e);
            postRoute();
            return;
        }
        try {
            // route过滤器
            route();
        } catch (ZuulException e) {
            error(e);
            postRoute();
            return;
        }
        try {
            // post过滤器
            postRoute();
        } catch (ZuulException e) {
            error(e);
            return;
        }

    } catch (Throwable e) {
        error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
    } finally {
        RequestContext.getCurrentContext().unset();
    }
}

根据上面的源码我们知道,一个请求到来的时候,就要经历preRoute、route、postRoute几个阶段,用官方的图来说明

zuul自定义route源码分析

网关请求执行的过程

根据第上面的内容,我们知道,当通过网关对服务进行请求的时候,要经历preRoute,route、postRoute阶段,这里以以preRoute()方法为例,对路由的处理过程进行说明。

preRoute()方法如下:

void preRoute() throws ZuulException {
    zuulRunner.preRoute();
}

ZuulRunner中的preRoute()方法如下:

public void preRoute() throws ZuulException {
    FilterProcessor.getInstance().preRoute();
}

FilterProcessor是一个单例模式,FilterProcessor中的preRoute()方法如下:

public void preRoute() throws ZuulException {
    try {
        // 运行pre过滤器
        runFilters("pre");
    } catch (ZuulException e) {
        throw e;
    } catch (Throwable e) {
        throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
    }
}

执行过滤器,runFilters()方法如下:

public Object runFilters(String sType) throws Throwable {
    if (RequestContext.getCurrentContext().debugRouting()) {
        Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
    }
    boolean bResult = false;
    // 根据过滤器类型,获取过滤器列表。
    List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
    if (list != null) {
        // 依次调用过滤器
        for (int i = 0; i < list.size(); i++) {
            ZuulFilter zuulFilter = list.get(i);
            // 过滤器处理过程
            Object result = processZuulFilter(zuulFilter);
            if (result != null && result instanceof Boolean) {
                bResult |= ((Boolean) result);
            }
        }
    }
    return bResult;
}

根据类型获取过滤器列表,getFiltersByType()方法如下:

public List<ZuulFilter> getFiltersByType(String filterType) {

    List<ZuulFilter> list = hashFiltersByType.get(filterType);
    if (list != null) return list;

    list = new ArrayList<ZuulFilter>();
    // 获取所有的过滤器
    Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
    for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
        ZuulFilter filter = iterator.next();
        // 取得filterType的类型列表
        if (filter.filterType().equals(filterType)) {
            list.add(filter);
        }
    }
    // 对filter进行排序
    Collections.sort(list); // sort by priority
    // 保存列表
    hashFiltersByType.putIfAbsent(filterType, list);
    return list;
}

filterRegistry类是一个单例模式,getAllFilters()方法如下

public class FilterRegistry {

    private static final FilterRegistry INSTANCE = new FilterRegistry();

    // ....省略....

    public Collection<ZuulFilter> getAllFilters() {
        return this.filters.values();
    }

}

过滤器的处理方法processZuulFilter()如下:

public Object processZuulFilter(ZuulFilter filter) throws ZuulException {

    RequestContext ctx = RequestContext.getCurrentContext();
    boolean bDebug = ctx.debugRouting();
    final String metricPrefix = "zuul.filter-";
    long execTime = 0;
    String filterName = "";
    try {
        long ltime = System.currentTimeMillis();
        filterName = filter.getClass().getSimpleName();
      
        // ....省略....
        
        // 运行filter
        ZuulFilterResult result = filter.runFilter();
        ExecutionStatus s = result.getStatus();
        execTime = System.currentTimeMillis() - ltime;
        
        // .....省略....
 
        usageNotifier.notify(filter, s);
        return o;

    } catch (Throwable e) {
        // .....省略.....
    }
}

runFilter()方法如下,:

public ZuulFilterResult runFilter() {
    ZuulFilterResult zr = new ZuulFilterResult();
    if (!isFilterDisabled()) {
        // 判断过滤器是否需要执行
        if (shouldFilter()) {
            Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
            try {
                // 调用filter的run方法。
                Object res = run();
                zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
            } catch (Throwable e) {
               // ....省略....
            } finally {
                t.stopAndLog();
            }
        } else {
            zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
        }
    }
    return zr;
}

最终调用到各ZuulFilter中的run()方法。

路由查找

pre类型的PreDecorationFilter过滤器,用来进行路由规则的匹配

如下:
zuul自定义route源码分析

执行后,上下文内容中的内容如下,加入了requestURI
zuul自定义route源码分析

访问服务

根据下图可以知道,真正访问服务的是route阶段。如下:
zuul自定义route源码分析

对于正常的服务,比如:/xxx/service_name是通过RibbonRoutingFilter实现对服务的负载均衡访问,它的run()方法如下:

public Object run() {
    RequestContext context = RequestContext.getCurrentContext();
    this.helper.addIgnoredHeaders();
    try {
        RibbonCommandContext commandContext = buildCommandContext(context);
        ClientHttpResponse response = forward(commandContext);
        setResponse(response);
        return response;
    }
    catch (ZuulException ex) {
        throw new ZuulRuntimeException(ex);
    }
    catch (Exception ex) {
        throw new ZuulRuntimeException(ex);
    }
}

如果是固定的url链接,如:http://www.abc.com/xxx/service_name这种,则是通过SendForwardFilter过滤器实现转发。它的run()方法如下:

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()) {
                // url转发
                dispatcher.forward(ctx.getRequest(), ctx.getResponse());
                ctx.getResponse().flushBuffer();
            }
        }
    }
    catch (Exception ex) {
        ReflectionUtils.rethrowRuntimeException(ex);
    }
    return null;
}

相关推荐