擴展Zuul實現ignored-patterns的byPass功能

前言

2018年年末的一天,咱們的架構師公司如何擴展Zuul時,說了1個功能,以下:java

  1. 對zuul的ignoredPath,加入了byPass(旁路功能),能夠單獨開放指定的url。 例如:公司屏蔽
/**/*Manage/*,
設置byPassUrl,/**/hello2Manage/*
這時全部知足/**/hello2Manage/* 均可以被外網訪問。

這個功能我以爲很通常,應該很是的簡單,完成也就10分鐘,結果哎,不說了都是淚啊!git

初步設想

zuul 能夠跟Eureka結合實現默認配置 zuul能夠設置zuul:ignored-patterns 用於設置屏蔽的url, 還能夠指定路由配置例如:github

zuul:
 route: hello-service-ext:
   path: /user-service/ext/*
   serviceId: user-service-ext

初步想法很簡單,就是在Zuul和Eureka實現默認配置基礎上,加入一個指定路由配置,以後再配置zuul:ignored-patterns爲了保證配置按順序生效YAML文件進行配置web

初次嘗試的錯誤配置以下spring

#********* 無效配置 **************
spring:
  application:
    name: api-gateway
server:
  port: 5555
# zuul 開啓特殊的url
# 忽略Mange結尾的數據
zuul:
 route:
  hello-service-ext:
   path: /hello-service/hello2Manage/*
   serviceId: hello-service
 ignored-patterns: /**/*Manage/*
eureka:
  client:
    service-url:
     defaultZone: http://localhost:1111/eureka
management:
  security:
   enabled: false

沒看過源碼的狀況下,果真的失敗了

本地eureka(註冊中心)上的服務以下: image
有2個服務1個api-gateway(即zuul服務),一個hello-service用於測試。 從圖上看個人zuul的端口號是5555,嘗試訪問 localhost:5555/hello-center/hello2Manage/helloapi

image

可是不使用路由能夠訪問
image架構

這說明配置沒有生效!mvc

查看源碼找辦法

回想spring mvc和zuul的知識點,有以下2點,引發了個人注意;app

  • spring mvc核心流程中有一步是從HandlerMapping中查找handler,
  • Zuul引入Eureka後,它會爲每個Eureka中的服務自動建立默認路由規則,默認規則爲以serviceId配置請求名做爲前綴。

Zuul應該是實現了本身的HandlerMapping?查找源碼發現ide

org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping類,該類實現了MVCHandlerMapping, 將傳入請求路徑映射到遠程服務的

在該類的lookupHandler中,找到了關於 IgnoredPaths的部分,

@Override
	protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
			return null;
		}
		//這裏有getIgnoredPaths,
		//就是獲取配置的zuul:ignored-patterns: /**/*Manage/*
		if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
		// 忽略部分源碼
	}

看到這我忽然靈光一現,spring cloud zuul的大神們必定不光在這1個地方進行了IgnoredPaths的判斷,由於不嚴謹,還須要在匹配遠程服務的時候在進行篩選。 順着這個思路我就往下找,註冊Handler的地方,仍是在ZuulHandlerMapping中

private void registerHandlers() {
		Collection<Route> routes = this.routeLocator.getRoutes();
		if (routes.isEmpty()) {
			this.logger.warn("No routes found from RouteLocator");
		}
		else {
			for (Route route : routes) {
				registerHandler(route.getFullPath(), this.zuul);
			}
		}
	}

能夠看出註冊handler,其實是依賴routeLocator,也就是RouteLocator這個接口的實現的。這個接口的聲明以下:

/**
 * @author Dave Syer
 */
public interface RouteLocator {

	/**
	 * 果真有一個Ignored route paths (or patterns), if any.
	 */
	Collection<String> getIgnoredPaths();

	/**
	 * A map of route path (pattern) to location (e.g. service id or URL).
	 */
	List<Route> getRoutes();

	/**
	 * Maps a path to an actual route with full metadata.
	 */
	Route getMatchingRoute(String path);

}

重點關注getMatchingRoute這個方法,由於他是實現路由匹配的規則,裏邊應該有IgnoredPaths 的邏輯。

這個接口,有不少實現,其中2個實現須要關注

  • SimpleRouteLocator 用於實現Zuul配置文件中聲明的路由關係
  • DiscoveryClientRouteLocator是SimpleRouteLocator用於實現從Eureka中默認配置路由關係

我仔細查看了SimpleRouteLocator類發現以下邏輯,果真有個matchesIgnoredPatterns方法用於過濾url,DiscoveryClientRouteLocator並無重寫這個方法。

public Route getMatchingRoute(final String path) {
		return getSimpleMatchingRoute(path);
	}
	protected Route getSimpleMatchingRoute(final String path) {
		//省略部分代碼
		String adjustedPath = adjustPath(path);
        //獲取url對應的路由信息
		ZuulRoute route = getZuulRoute(adjustedPath);
		return getRoute(route, adjustedPath);
	}
	protected ZuulRoute getZuulRoute(String adjustedPath) {
	// 果真有個校驗,IgnoredPath的地方
		if (!matchesIgnoredPatterns(adjustedPath)) {
			//省略部分源碼
		}
		return null;
	}	
	protected boolean matchesIgnoredPatterns(String path) {
		for (String pattern : this.properties.getIgnoredPatterns()) {
			log.debug("Matching ignored pattern:" + pattern);
			if (this.pathMatcher.match(pattern, path)) {
				log.debug("Path " + path + " matches ignored pattern " + pattern);
				return true;
			}
		}
		return false;
	}

擴展源碼注意事項

進過上述分析,要實現對ignoredPath的byPass(旁路功能),須要擴展3個類

  • ZuulHandlerMapping,重寫lookupHandler方法
  • SimpleRouteLocator,重寫matchesIgnoredPatterns方法
  • DiscoveryClientRouteLocator,重寫matchesIgnoredPatterns方法

由於實際擴展很簡單擴展的部分能夠,到碼雲我提供的源碼獲取。不在這贅述
擴展以後,仍是須要將其注入到Spring中。咱們擴展ZuulProxyAutoConfiguration,擴展方式以下:

@Configuration
public class ZuulProxyConfigurationExtend extends ZuulProxyAutoConfiguration {
    @Autowired
    ZuulConstantReload zuulConstantReload;
    @Autowired
    private ErrorController errorController;
    @Autowired
    private DiscoveryClient discovery;
    @Autowired
    private ServiceRouteMapper serviceRouteMapper;
    @Autowired(required = false)
    private Registration registration;

    @RefreshScope
    @ConfigurationProperties("zuul")
    @Primary
    @Bean
    public ZuulProperties zuulProperties() {
        return new ZuulProperties();
    }
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
        ZuulHandlerMapping mapping = new ZuulHandlerMappingExtend(routes, zuulController());
        mapping.setErrorController(this.errorController);
        return mapping;
    }
    @Bean
    public SimpleRouteLocator simpleRouteLocator() {
        SimpleRouteLocatorExtend simpleRouteLocator= new SimpleRouteLocatorExtend(this.server.getServletPrefix(), this.zuulProperties);
        return simpleRouteLocator;
    }

    @Bean
    public DiscoveryClientRouteLocator discoveryRouteLocator() {
        DiscoveryClientRouteLocatorExtend discoveryClientRouteLocatorExtend= new DiscoveryClientRouteLocatorExtend(this.server.getServletPrefix(),
                this.discovery, this.zuulProperties, this.serviceRouteMapper, this.registration);
        return discoveryClientRouteLocatorExtend;
    }

}

對應配置與其餘擴展類,傳送門

開源中國碼雲地址
github地址

總結

不管多小的擴展功能,瞭解內部原理是必須的 --- 溫安適 20190125

相關文章
相關標籤/搜索