基於zuul實現自定義路由源碼分析


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請求的生命周期

網關請求執行的過程

根據第上面的內容,我們知道,當通過網關對服務進行請求的時候,要經歷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過濾器,用來進行路由規則的匹配

如下:
根據請求路徑尋找png

執行后,上下文內容中的內容如下,加入了requestURI
4.png-71.1kB

訪問服務

根據下圖可以知道,真正訪問服務的是route階段。如下:
image_1cbu5ucct12mlcro4u215s5vkk1u.png-39.4kB

對於正常的服務,比如:/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;
}


免責聲明!

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



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