服务路由
对此 Zuul
提供了一种基于服务的路由方式。我们只需要维护请求地址与服务 ID 之间的映射关系即可,并且由于集成了 Ribbon
, Zuul 还可以在路由的时候通过 Eureka 实现负载调用。
具体配置:
zuul.routes.sbc-user.path=/api/user/** zuul.routes.sbc-user.serviceId=sbc-user
这样当输入 http://localhost:8383/api/user/getUserInfo/1
时就会路由到注册到 Eureka
中服务 ID 为 sbc-user
的服务节点,如果有多节点就会按照 Ribbon 的负载算法路由到其中一台上。
以上配置还可以简写为:
# 服务路由 简化配置 zuul.routes.sbc-user=/api/user/**
这样让我们访问
http://127.0.0.1:8383/api/user/userService/getUserByHystrix
时候就会根据负载算法帮我们路由到 sbc-user 应用上,如下图所示:
启动了两个 sbc-user 服务。
请求结果:
一次路由就算完成了。
在上面的配置中有看到 /api/user/**
这样的通配符配置,具体有以下三种配置需要了解:
?
只能匹配任意的单个字符,如/api/user/?
就只能匹配/api/user/x /api/user/y /api/user/z
这样的路径。
*
只能匹配任意字符,如/api/user/*
就只能匹配/api/user/x /api/user/xy /api/user/xyz
。
**
可以匹配任意字符、任意层级。结合了以上两种通配符的特点,如/api/user/**
则可以匹配/api/user/x /api/user/x/y /api/user/x/y/zzz
这样的路径,最简单粗暴!
谈到通配符匹配就不得不提到一个问题,如上面的 sbc-user
服务由于后期迭代更新,将 sbc-user 中的一部分逻辑抽成了另一个服务 sbc-user-pro
。新应用的路由规则是 /api/user/pro/**
,如果我们按照:
zuul.routes.sbc-user=/api/user/** zuul.routes.sbc-user-pro=/api/user/pro/**
进行配置的话,我们想通过 /api/user/pro/
来访问 sbc-user-pro
应用,却由于满足第一个路由规则,所以会被 Zuul 路由到 sbc-user
这个应用上,这显然是不对的。该怎么解决这个问题呢?
翻看路由源码
org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator
中的 locateRoutes()
方法:
/** * Compute a map of path pattern to route. The default is just a static map from the * {@link ZuulProperties}, but subclasses can add dynamic calculations. */ protected Map<String, ZuulRoute> locateRoutes() { LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>(); for (ZuulRoute route : this.properties.getRoutes().values()) { routesMap.put(route.getPath(), route); } return routesMap; }
发现路由规则是遍历配置文件并放入 LinkedHashMap
中,由于 LinkedHashMap
是有序的,所以为了达到上文的效果,配置文件的加载顺序非常重要,因此我们只需要将优先匹配的路由规则放前即可解决。
过滤器
过滤器可以说是整个 Zuul 最核心的功能,包括上文提到路由功能也是由过滤器来实现的。
摘抄官方的解释: Zuul 的核心就是一系列的过滤器,他能够在整个 HTTP
请求、响应过程中执行各样的操作。
其实总结下来就是四个特征:
- 过滤类型
- 过滤顺序
- 执行条件
- 具体实现
其实就是 ZuulFilter
接口中所定义的四个接口:
String filterType(); int filterOrder(); boolean shouldFilter(); Object run();
官方流程图(生命周期):
简单理解下就是:
当一个请求进来时,首先是进入 pre
过滤器,可以做一些鉴权,记录调试日志等操作。之后进入 routing
过滤器进行路由转发,转发可以使用 Apache HttpClient
或者是 Ribbon
。
post
过滤器呢则是处理服务响应之后的数据,可以进行一些包装来返回客户端。
error
则是在有异常发生时才会调用,相当于是全局异常拦截器。
自定义过滤器
接下来实现一个文初所提到的鉴权操作:
新建一个 RequestFilter
类继承与 ZuulFilter
接口
/** * Function: 请求拦截 * * @author crossoverJie * Date: 2017/11/20 00:33 * @since JDK 1.8 */ public class RequestFilter extends ZuulFilter { private Logger logger = LoggerFactory.getLogger(RequestFilter.class) ; /** * 请求路由之前被拦截 实现 pre 拦截器 * @return */ @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); String token = request.getParameter("token"); if (StringUtil.isEmpty(token)){ logger.warn("need token"); //过滤请求 currentContext.setSendZuulResponse(false); currentContext.setResponseStatusCode(400); return null ; } logger.info("token ={}",token) ; return null; } }
非常 easy,就简单校验下请求中是否包含 token
,不包含就返回 401 code。