擴展Zuul實現ignored-patterns的byPass功能
前言
2018年年底的一天,我們的架構師公司如何擴展Zuul時,說了1個功能,如下:
- 對zuul的ignoredPath,加入了byPass(旁路功能),可以單獨開放指定的url。 例如:公司屏蔽
/**/*Manage/*,
設置byPassUrl,/**/hello2Manage/*
這時所有滿足/**/hello2Manage/* 都可以被外網訪問。
這個功能我覺得很一般,應該非常的簡單,完成也就10分鐘,結果哎,不說了都是淚啊!
初步設想
zuul 可以跟Eureka結合實現默認配置 zuul可以設置zuul:ignored-patterns 用於設置屏蔽的url, 還可以指定路由配置例如:
zuul:
route: hello-service-ext:
path: /user-service/ext/*
serviceId: user-service-ext
初步想法很簡單,就是在Zuul和Eureka實現默認配置基礎上,加入一個指定路由配置,之後再配置zuul:ignored-patterns ,為了保證配置按順序生效YAML文件進行配置。
初次嘗試的錯誤配置如下:
#********* 無效配置 **************
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(註冊中心)上的服務如下:
有2個服務1個api-gateway(即zuul服務),一個hello-service用於測試。 從圖上看我的zuul的埠號是5555,嘗試訪問 localhost:5555/hello-center/hello2Manage/hello
但是不使用路由可以訪問
這說明配置沒有生效!
查看源碼找辦法
回想spring mvc和zuul的知識點,有如下2點,引起了我的注意;
- spring mvc核心流程中有一步是從HandlerMapping中查找handler,
- Zuul引入Eureka後,它會為每一個Eureka中的服務自動創建默認路由規則,默認規則為以serviceId配置請求名作為前綴。
Zuul應該是實現了自己的HandlerMapping?查找源碼發現
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地址
總結
無論多小的擴展功能,了解內部原理是必須的無論多小的擴展功能,了解內部原理是必須的
{!-- PGC_COLUMN --}
? 著作權歸作者所有
作者:溫安適
原文:https://my.oschina.net/floor/blog/3006144
※關於vs code文本編輯器的快捷鍵
※SQL語法和C井調用
TAG:程序員小新人學習 |