2018年年末的一天,咱們的架構師公司如何擴展Zuul時,說了1個功能,以下:java
/**/*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(註冊中心)上的服務以下: api
可是不使用路由能夠訪問
bash
這說明配置沒有生效!架構
回想spring mvc和zuul的知識點,有以下2點,引發了個人注意;mvc
Zuul應該是實現了本身的HandlerMapping?查找源碼發現app
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類發現以下邏輯,果真有個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個類
由於實際擴展很簡單擴展的部分能夠,到碼雲我提供的源碼獲取。不在這贅述 。
擴展以後,仍是須要將其注入到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;
}
}
複製代碼
不管多小的擴展功能,瞭解內部原理是必須的 --- 溫安適 20190125