扩展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;
}
}
对应配置与其他扩展类,传送门
总结
无论多小的扩展功能,了解内部原理是必须的
--- 温安适 20190125

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
如何写好C代码之依赖注入
依赖注入(Dependency Injection 简写为DI)开发过程中解除耦合的经典手段,但是似乎从一开始这货就是为面向对象而生的,我所看到的示例都没有将C语言考虑在内。难道C语言不能使用这么经典的设计模式?本文就来介绍一下C语言如实使用依赖注入来解除耦合。 参数注入 对应于面向对象语言的构造函数注入,C语言作为过程语言,参数注入法是最简单、也是最直接的方法。最常见的排序方法qsort就是用这种方法: void qsort(void* base, size_t num, size_t size, int (*compar)(const void*,const void*)); 可以看到qsort函数的第四个参数compar就是外部依赖的对象(函数),因为不同场景有不同的比较元素大小的方式,通过参数将外部依赖注入,使该函数更加具有通用型,因为实际上我们用qsort,只是用他的排序算法,其他的都是和具体使用场景有关。 设置(set)接口注入 上一篇我们介绍的设置回调函数的方法其实就是使用这种方法,其本质就是专门对外提供一个接口,用来将依赖的外部对象或者函数注入到本模块中来。比如开发一个...
-
下一篇
Kubernetes中Pod间共享内存方案
Author: xidianwangtao@gmail.com 摘要:一些公共服务组件在追求性能过程中,与业务耦合太紧,造成在制作基础镜像时,都会把这些基础组件都打包进去,因此当业务镜像启动后,容器里面一大堆进程,这让Kubernetes对Pod的管理存在很大隐患。为了让业务容器瘦身,更是为了基础组件自身的管理更独立和方便,将基础组件从业务镜像中剥离并DaemonSet容器化部署。然而一些基础组件Agent与业务Pod之间通过共享内存的方式进行通信,同一Node中跨Pod的共享内存方案是首先要解决的问题。 为什么要将公共基础组件Agent进行DaemonSet部署 自研的公共基础组件,比如服务路由组件、安全组件等,通常以进程方式部署在Node上并同时为Node上所有的业务提供服务,微服务及容器化之后,服务数量成百上千的增长,如果以sidecar或者打包到业务Image中继续Per Pod Per Agent的方式部署, 那么基础组件的Server端的压力可能也会成百上千的增长,风险是很大的。因此,我们希望能以DaemonSet方式部署这些组件的Agents。 先说说Kubernetes...
相关文章
文章评论
共有0条评论来说两句吧...