前言
- Zuul在
Spring Cloud Netfilx
体系中扮演着接入者网关的角色。
- 本质上来说Zuul本身就是一系列的filters, 可以类比Servlet框架的Filter。按照生命周期我们可以分为四种类型(pre,route,post,err)分别对应请求过程。你可以从
com.netflix.zuul.FilterProcessor
类里面找到所有的生命周期处理。
- 为什么我们要去了解它?比如我们想在网关统一对用户进行鉴权,进行
JWT
的解析和参数转换,比如我们想实现自己的httpClient,再比如我们想在后端业务微服务返回的结果内进行一些特别的处理,比如脱敏啊,比如去掉一些字段啊。
Zuul每个周期的流转过程
maven
- 在maven中加入sprin cloud 对zuul的gav
1
2
3
4
|
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
|
- 下面代码截取自
spring-cloud-starter-netflix-zuul-2.0.2.RELEASE
,弟131-138行。
- 在这里他激活了一个叫ZuulServlet的类,并把这个类注册给了spring。
1
2
3
4
5
6
7
8
9
|
//。
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(new ZuulServlet(),
this.zuulProperties.getServletPattern());
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}
|
- 我们继续追下去看看这个ZuulServlet到底是干了些什么。
- 下面代码截取自
spring-cloud-starter-netflix-zuul-2.0.2.RELEASE
,来自类com.netflix.zuul.http.ZuulServlet。去掉了一些干扰阅读的代码。
- 在分析代码之前我们先看
RequestContext
这么个鬼东西,他贯穿了zuul的处理的生命周期,核心作用就是通过ThreadLocal变量将每一给filter处理结果存储起来,进行传递到下一个filter。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class RequestContext extends ConcurrentHashMap<String, Object> {
protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
@Override
protected RequestContext initialValue() {
try {
return contextClass.newInstance();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
};
// 直接返回的就是ThreadLocal变量
public static RequestContext getCurrentContext() {
RequestContext context = threadLocal.get();
return context;
}
}
|
- 核心流程处理直接看到service方法,可以看到这里直接覆写了HttpServlet.service,我们知道所有的请求都会经过service方法,所以这里这里相当于是拦截了所有的请求.
先介绍一下每个route分别定位是什么:
-
preFilter:我们可以写一个我们自己的preFilter,在preFilter里面处理user的鉴权,JWT是否有效,参数转换,限流等到前置工作都能在此处实现,并可以通过向外抛出相关异常的方式传递到errorFilter中。
-
routeFilter:route阶段是发起http具体请求的阶段,可想而知如果我们使用ribbon做负载均衡的调用的话,ribbon一定会有一个route阶段的filter来处理具体的请求。没错这个类就RibbonRoutingFilter
它将zuul和ribbon连接起来了,而DiscoveryClientRouteLocator
又将ribbon和eurake又连接起来,这样通过从RequestContext中获取到serviceid,就能完成服务发现和服务调用啦。当然你可以新增一个自己的routeFilter来完成自己的http OR 其他协议的请求调用。
-
post: post阶段,这个阶段我们能从RequestContext中获取到route请求完成的结果,可以对ResponseBody进行一些特殊处理,也能添加一下个性化的HttpHeader
接下呢是try…catch三连.轻松的定义了四条路由链处理:
- 第一条链(正确的处理过程):
pre -> route -> post
- 第二条链(在pre过程中抛出异常):
pre -> error -> post
一般在pre抛出的异常都是我们自定义的异常。
- 第三条链(在route过程中抛出异常):
pre ->route ->error ->post
在实际使用中,一般是url编写错误,service-id书写有误,或者远程调用发生错误。
- 第四条链(在post过程中抛出异常):
pre ->route ->post ->error
一般在post抛出异常都是由于客户这边强制关闭了连接比如说客户请求过程中断网啊,比如说下载的excel过大导致客户关闭浏览器啊。
ZuulServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
public class ZuulServlet extends HttpServlet {
// 将实际的路由处理委托给了zuulRunner,即可我们前面提到的四个生命周期的具体调用者就是这位老哥了。后面讲具体讲到
private ZuulRunner zuulRunner;
// 正常的
@Override
public void init(ServletConfig config) throws ServletException {
// other code...
// 在这里激活了zuulRuner
zuulRunner = new ZuulRunner(bufferReqs);
}
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
//委托给zuulRunner
void postRoute() throws ZuulException {
zuulRunner.postRoute();
}
//委托给zuulRunner
void route() throws ZuulException {
zuulRunner.route();
}
//委托给zuulRunner
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
//委托给zuulRunner
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
zuulRunner.init(servletRequest, servletResponse);
}
//委托给zuulRunner,并将异常放入ThreadLocal 进行传递
void error(ZuulException e) {
RequestContext.getCurrentContext().setThrowable(e);
zuulRunner.error();
}
}
|
ZuulRunner
- 可以看到zuulRunner好像也没干实际干事儿,又将调用委托给了一个单例对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class ZuulRunner {
public void postRoute() throws ZuulException {
FilterProcessor.getInstance().postRoute();
}
public void route() throws ZuulException {
FilterProcessor.getInstance().route();
}
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
public void error() {
FilterProcessor.getInstance().error();
}
}
|
FilterProcessor
- 接下来我们在看
FilterProcessor
这个类,这个类总算是干活儿的小老铁了。
- 一下代码截取自FilterProcessor。有没有觉得很熟悉呀,这里我们用肉眼就能看出来是每个方法在调用哪个filter,调用方式也是很简单粗暴,传入调用的filterType。那么如果你需要debugZuul的生命周期,断点打在这部分方法上是不是极好的呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
public void postRoute() throws ZuulException {
try {
runFilters("post");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
}
}
/**
* runs all "error" filters. These are called only if an exception occurs. Exceptions from this are swallowed and logged so as not to bubble up.
*/
public void error() {
try {
runFilters("error");
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
/**
* Runs all "route" filters. These filters route calls to an origin.
*
* @throws ZuulException if an exception occurs.
*/
public void route() throws ZuulException {
try {
runFilters("route");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
}
}
/**
* runs all "pre" filters. These filters are run before routing to the orgin.
*
* @throws ZuulException
*/
public void preRoute() throws ZuulException {
try {
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
|
- 这个类有个关键的方法
runFilters
这个方法传入的的是一个filterType即要进行route类型,即会传入(pre,route,post,err)这些个类型按照上面分析的流程进行调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public Object runFilters(String filterType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
// 这里是获取到同一个类型的Filter集合
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
//这里又把调用委托到出去了 实际上我们可以看成 zuulFilter.run();调用
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
|
- 到处我们就将filter的请求调用周期分析完毕。下一篇文章我们将具体分析到每个阶段具体的Filter都干了些什么。