首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…
一. 前言
这一篇来学习一下Zuul 主处理流程 , 以下先来了主流程图 :
二. 使用案例
通过配置文件就可以实现基本的路由规则
zuul:
routes:
test:
url: http://127.0.0.1:8086/
三. 源码梳理
3.1 配置的加载与初始化
这里来看一下配置是通过什么方式加载到系统中的 , 首先看一下自动装配类 :
Zuul 的自动装配类是 ZuulServerAutoConfiguration , 它存在如下属性 :
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// 注入的类包括如下Bean :
- HasFeatures :
- CompositeRouteLocator :
- SimpleRouteLocator :
- ZuulController :
- ZuulHandlerMapping :
- ApplicationListener :
- ServletRegistrationBean :
// Filter Bean 列表 :
- ServletDetectionFilter :
- FormBodyWrapperFilter :
- DebugFilter :
- Servlet30WrapperFilter :
- SendResponseFilter :
- SendErrorFilter :
- SendForwardFilter :
// 其中包括三个 Configuration :
- ZuulFilterConfiguration :
- ZuulCounterFactoryConfiguration :
- ZuulMetricsConfiguration
其中核心的处理类就是 ZuulRefreshListener , 它继承于 ApplicationListener , 在 Application 创建时调用 , 其中主要会调用 zuulHandlerMapping
// Step 1 : reset 调用入口
private void reset() {
// ((RefreshableRouteLocator) this.routeLocator).refresh();
this.zuulHandlerMapping.setDirty(true);
}
// Step 2 : 刷新 routeLocator
for (RouteLocator locator : routeLocators) {
if (locator instanceof RefreshableRouteLocator) {
((RefreshableRouteLocator) locator).refresh();
}
}
// Step 3 : 添加 routes
protected void doRefresh() {
this.routes.set(locateRoutes());
}
// Step 4 : 创建 routes
protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
// 调用 super local , 从 Properties 中获取配置
routesMap.putAll(super.locateRoutes());
if (this.discovery != null) {
Map<String, ZuulRoute> staticServices = new LinkedHashMap<>();
for (ZuulRoute route : routesMap.values()) {
// 核心逻辑 , 加载 Properties 文件的配置到 Map 中
String serviceId = route.getServiceId();
staticServices.put(serviceId, route);
}
// 通过 DiscoveryClient从注册中心拉取关联
List<String> services = this.discovery.getServices();
// 从 Proerties 中获取 ignore 列表
String[] ignored = this.properties.getIgnoredServices().toArray(new String[0]);
for (String serviceId : services) {
// 循环所有 services , 生成对应 key
String key = "/" + mapRouteToService(serviceId) + "/**";
// 这里会出现2种情况 :
// 一 : 如果已存在且不存在 url ,则为其设置 location
if (staticServices.containsKey(serviceId)&& staticServices.get(serviceId).getUrl() == null) {
ZuulRoute staticRoute = staticServices.get(serviceId);
if (!StringUtils.hasText(staticRoute.getLocation())) {
staticRoute.setLocation(serviceId);
}
}
// 二 : 如果不在 ignore 队列中且之前不存在
if (!PatternMatchUtils.simpleMatch(ignored, serviceId)&& !routesMap.containsKey(key)) {
routesMap.put(key, new ZuulRoute(key, serviceId));
}
}
}
// 附加流程 : 如果存在 DEFAULT_ROUTE , 则将 routesMap 覆盖 DEFAULT_ROUTE
if (routesMap.get(DEFAULT_ROUTE) != null) {
ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE);
routesMap.remove(DEFAULT_ROUTE);
routesMap.put(DEFAULT_ROUTE, defaultRoute);
}
LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
String path = entry.getKey();
// 主要逻辑为构建 url , 为其添加前缀 / 和 prefix
values.put(path, entry.getValue());
}
// 最终返回对象
return values;
}
// Step 4-1 L
protected Map<String, ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
// 从 properties 中获取 Route 列表 , 构建 routeMap
for (ZuulRoute route : this.properties.getRoutes().values()) {
routesMap.put(route.getPath(), route);
}
return routesMap;
}
至此 , Route 全部扫描完成 , 开始进入处理逻辑
3.2 请求的拦截
Zuul 请求的处理同样是通过 Servlet 进行的 , 前面自动装配中生成了 , 其主要入口为 : ZuulController , 它由 MVC 发起流程 , 主要流程如下 :
Step 1 : 拦截的入口
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
try {
// 实际调用父类 , 父类会继续调用 service , 到 ZuulServlet
return super.handleRequestInternal(request, response);
}
finally {
RequestContext.getCurrentContext().unset();
}
}
Step 2 : ZuulServlet 处理请求
屏蔽相关的 try - catch 后 , 留下主要的方法如下 :
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
// Step 1 : 初始化请求 > zuulRunner.init(servletRequest, servletResponse)
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Step 2 : 为容器设置标识符为 zuul 引擎 -> put("zuulEngineRan", true);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
// Step 3 : route 前置处理 , 主要是 filter 处理
preRoute();
// Step 4 :route 路由
route();
// Step 5 : 后置处理 ,返回 Response
postRoute();
}
Step 3 :通用 filter 进行处理
以上的三四五最终都是对 Filter 的执行 , 也是 Zuul 的核心逻辑 , 通过 Filter 进行处理 , 整体的核心代码为 : FilterProcessor.getInstance().route()
, 不同的是三种不同的入口会传入三种不同的 type 进行处理 -> pre / route / post
// C- FilterProcessor
public Object runFilters(String sType) throws Throwable {
boolean bResult = false;
// 获取当前类型所有的 ZuulFilter
// type 有多种 : pre / route / post , 这也意味着我们有三个切入点
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
// Step 2 : 循环 list 所有的 filter , 并且发起 ZuulFilter Process 进行处理
Object result = processZuulFilter(zuulFilter);
return bResult;
}
// 发起 Filter 的调用
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
boolean bDebug = ctx.debugRouting();
final String metricPrefix = "zuul.filter-";
long execTime = 0;
String filterName = "";
try {
// Time 1 : 记录开始时间
long ltime = System.currentTimeMillis();
filterName = filter.getClass().getSimpleName();
RequestContext copy = null;
// 准备返回对象 ,分别承接异常和正常结果
Object o = null;
Throwable t = null;
// 核心 !! 调用 Filter 进行处理
// 注意 , 由于forward 等都是 filter 进行的处理 , 所以存在状态和耗时
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus();
// Time 2 : 记录结束时间
execTime = System.currentTimeMillis() - ltime;
switch (s) {
case FAILED:
// 调用异常 , 则获取其中 Exception 进行处理
t = result.getException();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
break;
case SUCCESS:
// 调用成功 , 则获取 result
o = result.getResult();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
break;
default:
break;
}
if (t != null) throw t;
usageNotifier.notify(filter, s);
return o;
} catch (Throwable e) {
// 异常处理会构建 ZuulException 进行返回 , 意味着可以捕捉该 Exception 进行处理
}
}
Step 4 : 执行 FIlter
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
// 判断 filter 是否打开 且需要过滤
if (!isFilterDisabled()) {
if (shouldFilter()) {
// 准备异常对象
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
// run 执行 filter 逻辑 , 会由实际的 filter 实现 , 并且构建返回体
Object res = run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
// 此处就是构建 FAILED 状态 , 并且被外部拦截
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(e);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
补充 :preRoute 相关 Filter
org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter com.gang.zuul.service.demo.config.DefaultZuulFilter org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter
3.3 请求的重定向
ZuulServlet 的 service 中核心第二步就是发起重定向 ,同样的 , 这里会有多个 filter 进行处理 , 包括 :
- RibbonRoutingFilter : Ribbon 负载均衡
- SimpleHostRoutingFilter : 基础 route 类
- SendForwardFilter : 重定向转发
public Object run() {
// Step 1 : 从 Context 中获取请求对象
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
// Step 2 : 获取 Header 头 和 Param 属性
MultiValueMap<String, String> headers = this.helper.buildZuulRequestHeaders(request);
MultiValueMap<String, String> params = this.helper.buildZuulRequestQueryParams(request);
String verb = getVerb(request);
// Step 3 : 从 request 中获取输入流 , PS : 流这种对象通常只能获取一次
InputStream requestEntity = getRequestBody(request);
if (getContentLength(request) < 0) {
context.setChunkedRequestBody();
}
// Step 4 : 构建请求 URL -> /demo/start/get
String uri = this.helper.buildZuulRequestURI(request);
this.helper.addIgnoredHeaders();
try {
// Step 5 : 发起远程调用
CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
headers, params, requestEntity);
// Step 6 : 把 Response 设置到容器中
// RequestContext.getCurrentContext().set("zuulResponse", response);
// this.helper.setResponse
setResponse(response);
}
catch (Exception ex) {
throw new ZuulRuntimeException(handleException(ex));
}
return null;
}
补充请求环节 :
针对上一部分 forward 的处理
// 1. 构建 host
URL host = RequestContext.getCurrentContext().getRouteHost();
HttpHost httpHost = getHttpHost(host);
// 2. 构建 InputStreamEntity
new InputStreamEntity(requestEntity, contentLength,contentType);
// 3. 构建 HttpRequest
buildHttpRequest(verb, uri, entity, headers, params,request);
// 4. HttpClient 发起远程调用
httpclient.execute(httpHost, httpRequest)
3.4 返回路由结果
其主要通过类 SendResponseFilter 实现 , 核心属性为 private ThreadLocal<byte[]> buffers
, 其中包括二个主要的步骤 :
- addResponseHeaders () : 写 Response Header 头
- writeResponse () : 写 ResponseBody
private void addResponseHeaders() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
// 如果开启了 DebugHeader , 则会添加对应Header 头
// public static final String X_ZUUL_DEBUG_HEADER = "X-Zuul-Debug-Header";
// 获取 context.get(ROUTING_DEBUG_KEY) , for 循环后添加到下方
// servletResponse.addHeader(X_ZUUL_DEBUG_HEADER, debugHeader.toString());
// 遍历容器中的 ZuulResponseHeaders , 添加到 servletResponse 中
List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
if (zuulResponseHeaders != null) {
for (Pair<String, String> it : zuulResponseHeaders) {
if (!ZuulHeaders.CONTENT_ENCODING.equalsIgnoreCase(it.first())) {
servletResponse.addHeader(it.first(), it.second());
}
}
}
// 如果需要添加 LengthHeader , context.getOriginContentLength() + servletResponse.setContentLength
// TODO :省略详情
}
写入 Response 主逻辑
private void writeResponse() throws Exception {
RequestContext context = RequestContext.getCurrentContext();
// there is no body to send
if (context.getResponseBody() == null
&& context.getResponseDataStream() == null) {
return;
}
// 从容器中获取 response , 并且设置 CharacterEncoding
HttpServletResponse servletResponse = context.getResponse();
if (servletResponse.getCharacterEncoding() == null) { // only set if not set
servletResponse.setCharacterEncoding("UTF-8");
}
// 核心 : 总体还是通过流进行输出
String servletResponseContentEncoding = getResponseContentEncoding(context);
OutputStream outStream = servletResponse.getOutputStream();
InputStream is = null;
try {
// 区别 byte 流和 ZIP 流
if (context.getResponseBody() != null) {
String body = context.getResponseBody();
is = new ByteArrayInputStream(
body.getBytes(servletResponse.getCharacterEncoding()));
}
else {
is = context.getResponseDataStream();
if (is != null && context.getResponseGZipped()) {
// 在发送到客户端之前解压流 , 将 gzip 流总结发到客户端
if (isGzipRequested(context)) {
servletResponseContentEncoding = "gzip";
}
else {
servletResponseContentEncoding = null;
is = handleGzipStream(is);
}
}
}
// 往 reponse 设置 header , encoding 类型
if (servletResponseContentEncoding != null) {
servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING,servletResponseContentEncoding);
}
if (is != null) {
// 往返回写 response
writeResponse(is, outStream);
}
}
finally {
// 关闭流等操作
- is.close();
- buffers.remove();
- (Closeable) zuulResponse).close();
}
}
总结
总得来说 , Zuul 的核心就是 Filter 拦截 ,从请求中取出 Request , 再通过 route 进行转发 , 在此期间进行各种复杂的处理 , 整体来说 ,结构非常清晰简单
其中部分 Filter 例如负载均衡这次都没有深入 , 后续再来详细看看