一、基本概念

1.1 Zuul网关过滤器类型

pre:优先执行的过滤器,其中自动配置中配置的PreDecorationFilter,主要负责决定该请求的路由以及发送给下游服务的额外请求头。

route:在pre过滤器后执行的过滤器。当zuul网关中为某一路由名称配置的是serviceId时,由ribbonRoutingFilter采用ribbion的方式决定请求下游服务地址并且进行远程调用,并将响应结果写入的RequestContext中;当zuul网关中为某一路由名称配置的是url时,由SimpleHostRoutingFilter采用apache httpClient的方式,将路由配置的URL与请求URI拼接作为下游服务请求URL并进行远程调用,将响应结果写入的RequestContext中。

error:pre、route过滤器中任何步骤出错都会执行error过滤器。

post:在pre、route、error类型的过滤器执行后执行。自动配置中的SendResponseFilter过滤器将RequestContext中存放的远程调用返回的响应头、响应体等信息组成响应信息响应给客户端。

1.2 Zuul网关过滤器执行优先级

从类型来看:pre–>route–>(error)—>post

从指定的order来看:oder越小,越优先执行

1.3 Zuul网关过滤器执行条件

Zuul网关过滤器执行条件由父类ZuulFilter中的方法shouldFilter返回值决定,true表示执行,false表示不执行。

二、Zuul网关原理

2.1 Zuul网关架构图

三、源码解读

以spring-cloud-netflix-zuul-2.2.9.RELEASE为基础解读Zuul网关源码

3.1 与Zuul网关相关的SpringBoot自动配置

spring-cloud-netflix-zuul-2.2.9.RELEASE包中的spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration

ZuulServerAutoConfiguration配置类注入的比较重要的Bean:

CompositeRouteLocator:主要负责根据path以及路由配置决定路由。

ZuulController:网关接收客户端请求的统一Controller

ZuulServlet:执行网关逻辑的入口

FormBodyWrapperFilter:解析客户端表单数据,并为下游服务重新编码。

SendResponseFilter:当网关没有发生异常时,从RequestContext取出下游服务的响应信息,并将它们组装成返回给浏览器的响应信息并响应。

SendErrorFilter:当网关发生异常时,从RequestContext取出异常信息组装成返回给浏览器的响应信息并响应。

ZuulProxyAutoConfiguration配置类注入的比较重要的Bean:

PreDecorationFilter:主要负责决定客户端请求的路由以及发送给下游服务的额外请求头。

RibbonRoutingFilter:当zuul网关中为某一路由名称配置的是serviceId时,由ribbonRoutingFilter采用ribbion的方式决定请求下游服务地址并且进行远程调用,并将响应结果写入的RequestContext中。

配置示例:

zuul:routes:goods:path: /goods/**serviceId: goods

SimpleHostRoutingFilter:当zuul网关中为某一路由名称配置的是url时,由SimpleHostRoutingFilter采用apache httpClient的方式,将路由配置的URL与请求URI拼接作为下游服务请求URL并进行远程调用,将响应结果写入的RequestContext中。

zuul:routes:goods:path: /goods/**url: http://localhost:8081

例如:请求到网关的URL为localhost:8090/goods/hello,则网关请求下游服务的URL为:http://localhost:8081/hello。

3.2 Zuul网关执行逻辑

3.2.1 由ZuulController统一接收浏览器请求。

ZuulController#handleRequest()

@Override
public ModelAndView handleRequest(HttpServletRequest request,HttpServletResponse response) throws Exception {try {// We don't care about the other features of the base class, just want to                // handle the requestreturn super.handleRequestInternal(request, response);}finally {// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilterRequestContext.getCurrentContext().unset();}
}

handleRequestInternal方法:

protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {Assert.state(this.servletInstance != null, "No Servlet instance");this.servletInstance.service(request, response);return null;
}

3.2.2由ZuulServlet中的service方法执行逻辑:

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {try {this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);RequestContext context = RequestContext.getCurrentContext();context.setZuulEngineRan();try {this.preRoute();} catch (ZuulException var13) {this.error(var13);this.postRoute();return;}try {this.route();} catch (ZuulException var12) {this.error(var12);this.postRoute();return;}try {this.postRoute();} catch (ZuulException var11) {this.error(var11);}} catch (Throwable var14) {this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));} finally {RequestContext.getCurrentContext().unset();}}

ZuulServlet中的service中首先调用init方法,将浏览器的request和response存放到RequestContext。RequestContext继承ConcurrentHashMap并且存放在threadLocal,作用域在一次请求中。调用完init方法,执行preRoute方法。

FilterProcessor#preRoute():

public void preRoute() throws ZuulException {try {this.runFilters("pre");} catch (ZuulException var2) {throw var2;} catch (Throwable var3) {throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName());}
}

preRoute方法执行类型为“pre”的过滤器。

FilterProcessor#runFilte():

public Object runFilters(String sType) throws Throwable {if (RequestContext.getCurrentContext().debugRouting()) {Debug.addRoutingDebug("Invoking {" + sType + "} type filters");}boolean bResult = false;List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);if (list != null) {for(int i = 0; i < list.size(); ++i) {ZuulFilter zuulFilter = (ZuulFilter)list.get(i);Object result = this.processZuulFilter(zuulFilter);if (result != null && result instanceof Boolean) {bResult |= (Boolean)result;}}}return bResult;
}

在根据过滤器类型名称获取过滤器方法getFiltersByType中获取对应的过滤器并调用Collections.sort(list)方法对过滤器排序。

public List<ZuulFilter> getFiltersByType(String filterType) {List<ZuulFilter> list = (List)this.hashFiltersByType.get(filterType);if (list != null) {return list;} else {List<ZuulFilter> list = new ArrayList();Collection<ZuulFilter> filters = this.filterRegistry.getAllFilters();Iterator iterator = filters.iterator();while(iterator.hasNext()) {ZuulFilter filter = (ZuulFilter)iterator.next();if (filter.filterType().equals(filterType)) {list.add(filter);}}Collections.sort(list);this.hashFiltersByType.putIfAbsent(filterType, list);return list;}
}

ZuulFilter#compareTo方法表明默认根据order从小到大排序,即在后续的对过滤器进行for循环执行时,order越小越优先执行。

public int compareTo(ZuulFilter filter) {return Integer.compare(this.filterOrder(), filter.filterOrder());
}

for循环为每个过滤器执行processZuulFilter方法,processZuulFilter方法中调用runFilter方法,如果过滤器中的shouldFilter返回true时执行ZuulFilter#run方法

public ZuulFilterResult runFilter() {ZuulFilterResult zr = new ZuulFilterResult();if (!this.isFilterDisabled()) {if (this.shouldFilter()) {//省略代码..try {Object res = this.run();//省略代码..} catch (Throwable var7) {//省略代码..} finally {//省略代码..}} else {zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);}}return zr;}

执行完preRoute()方法,若出现异常,则依次执行类型为error、post的过滤器,逻辑与pre过滤器一致,区别在于过滤器类型不一样;否则依次执行route()、postRoute()方法。

3.3 主要过滤器执行逻辑

详情解释见代码注释

3.3.1 pre类型

SevletDetectionFilter:决定由ZuulServlet还是DispatchServlet执行请求。

@Override
public Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();if (!(request instanceof HttpServletRequestWrapper)&& isDispatcherServletRequest(request)) {//由DispatchServlet执行请求时,HttpServletRequest没有被包装过并且Attributes中包含//DispatchServlet上下文参数ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);}else {//由ZuulServlet执行请求ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);}return null;
}

FormBodyWrapperFilter:表单数据解析过滤器

@Override
public boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();String contentType = request.getContentType();//GET 请求不执行if (contentType == null) {return false;}//仅处理表单数据和在DispatchServlet中的Multipart数据。try {MediaType mediaType = MediaType.valueOf(contentType);return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)|| (isDispatcherServletRequest(request)&& MediaType.MULTIPART_FORM_DATA.includes(mediaType));}catch (InvalidMediaTypeException ex) {return false;}
}@Override
public Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();FormBodyRequestWrapper wrapper = null;if (request instanceof HttpServletRequestWrapper) {HttpServletRequest wrapped = (HttpServletRequest) ReflectionUtils.getField(this.requestField, request);//FormBodyRequestWrapper其实就是将request中的表单数据提取出来wrapper = new FormBodyRequestWrapper(wrapped);//把wrapper对象赋值给request中的request属性,供后续使用ReflectionUtils.setField(this.requestField, request, wrapper);//若请求已经被包装过,则将wrapper对象赋值给包装过的request中的request属性,供后续使用if (request instanceof ServletRequestWrapper) {ReflectionUtils.setField(this.servletRequestField, request, wrapper);}}else {wrapper = new FormBodyRequestWrapper(request);ctx.setRequest(wrapper);}if (wrapper != null) {ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());}return null;
}

PreDecorationFilter:主要负责决定客户端请求的路由以及发送给下游服务的额外请求头。

@Override
public boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();return !ctx.containsKey(FORWARD_TO_KEY) //还未经过决定请求转发的过滤器&& !ctx.containsKey(SERVICE_ID_KEY); //还未经过决定serviceId的过滤器
}
@Override
public Object run() {RequestContext ctx = RequestContext.getCurrentContext();final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());if (insecurePath(requestURI)) {throw new InsecureRequestPathException(requestURI);}//根据URI以及配置的属性获取路由Route route = this.routeLocator.getMatchingRoute(requestURI);if (route != null) {String location = route.getLocation();if (location != null) {ctx.put(REQUEST_URI_KEY, route.getPath());ctx.put(PROXY_KEY, route.getId());if (!route.isCustomSensitiveHeaders()) {//将需要过滤的敏感头信息放入RequestContextthis.proxyRequestHelper.addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0]));}else {this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0]));}if (route.getRetryable() != null) {ctx.put(RETRYABLE_KEY, route.getRetryable());}//网关配置为urlif (location.startsWith(HTTP_SCHEME + ":")|| location.startsWith(HTTPS_SCHEME + ":")) {//设置下游服务的地址,供后续SimpleHostRoutingFilter(以httpClient的方式请求下游服务)使用ctx.setRouteHost(getUrl(location));ctx.addOriginResponseHeader(SERVICE_HEADER, location);}//网关配置的url以forward:开头,表明请求转发。else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {//去除forward标识字符,重新设置下游服务url放入RequestContext,//供后续SendForwardFilter过滤器使用        ctx.set(FORWARD_TO_KEY,StringUtils.cleanPath(location.substring(FORWARD_LOCATION_PREFIX.length())                                           + route.getPath()));ctx.setRouteHost(null);return null;}else {//设置serviceId,供后续RibbonRoutingFilter使用//由注册中心以及ribbon负载均衡决定最终下游服务地址ctx.set(SERVICE_ID_KEY, location);ctx.setRouteHost(null);ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);}//配置文件是否设置加入代理头//X-Forwarded-Host:请求的主机列表,用逗号隔开,越靠后越接近服务器//X-Forwarded-Port:请求的端口列表,用逗号隔开,越靠后越接近服务器//Forwarded-Proto:请求的协议列表,用逗号隔开,越靠后越接近服务器//X-Forwarded-Prefix:请求网关的前缀//X-Forwarded-For:请求的ip列表,用逗号隔开,越靠后越接近服务器if (this.properties.isAddProxyHeaders()) {addProxyHeaders(ctx, route);String xforwardedfor = ctx.getRequest().getHeader(X_FORWARDED_FOR_HEADER);String remoteAddr = ctx.getRequest().getRemoteAddr();if (xforwardedfor == null) {xforwardedfor = remoteAddr;}else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicatesxforwardedfor += ", " + remoteAddr;}ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);}if (this.properties.isAddHostHeader()) {ctx.addZuulRequestHeader(HttpHeaders.HOST,toHostHeader(ctx.getRequest()));}}}else {log.warn("No route found for uri: " + requestURI);String forwardURI = getForwardUri(requestURI);ctx.set(FORWARD_TO_KEY, forwardURI);}return null;
}

3.3.2 route类型

RibbonRoutingFilter:由Ribbon负载均衡决定下游服务地址并且进行请求,将下游服务响应结果存入RequestContext

@Override
public boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();//以serviceId的方式配置路由时执行该过滤器return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null&& ctx.sendZuulResponse());
}
@Override
public Object run() {RequestContext context = RequestContext.getCurrentContext();this.helper.addIgnoredHeaders();try {//根据请求头、请求参数、请求体、请求方法、serviceId、loadBalancerKey等构成//ribbon上下文RibbonCommandContext commandContext = buildCommandContext(context);//创建command并且进行远程调用ClientHttpResponse response = forward(commandContext);//将下游服务响应结果存放在RequestContext,供后post过滤器SendResponseFilter使用。setResponse(response);return response;}catch (ZuulException ex) {throw new ZuulRuntimeException(ex);}catch (Exception ex) {throw new ZuulRuntimeException(ex);}
}

SimpleHostRoutingFilter:以简单Apache HttpClient的方式调用下游服务

@Override
public boolean shouldFilter() {//路由配置的是URL时执行该过滤器return RequestContext.getCurrentContext().getRouteHost() != null&& RequestContext.getCurrentContext().sendZuulResponse();
}@Override
public Object run() {RequestContext context = RequestContext.getCurrentContext();HttpServletRequest request = context.getRequest();//组装请求头,将忽略的请求头过滤MultiValueMap<String, String> headers = this.helper.buildZuulRequestHeaders(request);//组装请求参数MultiValueMap<String, String> params = this.helper.buildZuulRequestQueryParams(request);//组装请求方法String verb = getVerb(request);InputStream requestEntity = getRequestBody(request);if (getContentLength(request) < 0) {context.setChunkedRequestBody();}String uri = this.helper.buildZuulRequestURI(request);this.helper.addIgnoredHeaders();try {//远程调用下游服务CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,headers, params, requestEntity);//保存下游服务响应结果setResponse(response);}catch (Exception ex) {throw new ZuulRuntimeException(handleException(ex));}return null;
}

SendForwardFilter:当网关配置的URL中含有forward标识时,执行的请求转发过滤器

@Override
public boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();//需要请求转发时但还未执行请求转发的情况下执行该过滤器return ctx.containsKey(FORWARD_TO_KEY)&& !ctx.getBoolean(SEND_FORWARD_FILTER_RAN, false);
}
@Override
public Object run() {try {RequestContext ctx = RequestContext.getCurrentContext();String path = (String) ctx.get(FORWARD_TO_KEY);RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);if (dispatcher != null) {//表示请求转发了ctx.set(SEND_FORWARD_FILTER_RAN, true);if (!ctx.getResponse().isCommitted()) {//请求转发dispatcher.forward(ctx.getRequest(), ctx.getResponse());//将response清空,这样后续的响应客户端的过滤器SendResponseFilter就不会执行了ctx.getResponse().flushBuffer();}}}catch (Exception ex) {ReflectionUtils.rethrowRuntimeException(ex);}return null;
}

3.3.3 post类型

SendResponseFilter:将响应信息从RequestContext中取出并组装,响应给浏览器。

@Override
public boolean shouldFilter() {RequestContext context = RequestContext.getCurrentContext();//不发生异常,下游服务有响应并且响应信息没被清空时执行return context.getThrowable() == null&& (!context.getZuulResponseHeaders().isEmpty()|| context.getResponseDataStream() != null|| context.getResponseBody() != null);
}
@Override
public Object run() {try {//组装下游服务的响应头和浏览器响应头addResponseHeaders();//响应给浏览器writeResponse();}catch (Exception ex) {ReflectionUtils.rethrowRuntimeException(ex);}return null;
}

参考地址:

1.Zuul网关官网文档

2.关于x-forward-for的解释

3.请求转发和重定向后面的代码是否执行

Zuul网关原理以及源码解析相关推荐

  1. Zuul网关原理及源码解读(草稿版)

    网关的本质就是一系列的过滤器Filter. 断点调试快捷键:F5进入方法,F6下一行,F7跳出方法,F8下一个断点.这些是活的,每个IDE都不一样. shouldFilter:true执行 反之不执行 ...

  2. 【特征匹配】ORB原理与源码解析

    相关 : Fast原理与源码解析 Brief描述子原理与源码解析 Harris原理与源码解析 http://blog.csdn.net/luoshixian099/article/details/48 ...

  3. Redis进阶- Redisson分布式锁实现原理及源码解析

    文章目录 Pre 用法 Redisson分布式锁实现原理 Redisson分布式锁源码分析 redisson.getLock(lockKey) 的逻辑 redissonLock.lock()的逻辑 r ...

  4. PCA-SIFT原理及源码解析

    相关: SIFT原理与源码解析 SURF原理与源码解析 ORB原理与源码解析 FAST原理与源码解析 BRIEF描述子原理与源码解析 Harris原理与源码解析 转载请注明出处:http://blog ...

  5. Spring Boot 核心原理与源码解析 - 目录

    准备重新写 SpringBoot 配置文件解析原理 , 先在这里把要写的内容记下来 Spring Boot 核心原理与源码解析 - 目录 1\何时解析\如何解析 application.propert ...

  6. 【特征匹配】BRIEF特征描述子原理及源码解析

    相关:Fast原理及源码解析 Harris原理及源码解析 SIFT原理及源码解析 SURF原理及源码解析 转载请注明出处: http://blog.csdn.net/luoshixian099/art ...

  7. 视频教程-YOLOv3目标检测:原理与源码解析-计算机视觉

    YOLOv3目标检测:原理与源码解析 大学教授,美国归国博士.博士生导师:人工智能公司专家顾问:长期从事人工智能.物联网.大数据研究:已发表学术论文100多篇,授权发明专利10多项 白勇 ¥78.00 ...

  8. SpringMVC工作原理及源码解析

    SpringMVC工作原理及源码解析 一:SpringMVC原理图 二:SpringMVC的主要组件 1.前端控制器DispatcherServlet: 2.处理器映射器HandlerMapping: ...

  9. Mybatis运行原理及源码解析

    Mybatis源码解析 一.前言 本文旨在mybatis源码解析,将整个mybatis运行原理讲解清楚,本文代码地址: https://github.com/lchpersonal/mybatis-l ...

最新文章

  1. x86 下制作 ARM Docker 镜像,Docker Hub、Travis 自动构建 qemu-user-static
  2. 微信自动回复和自动抢红包实现原理(三):自动抢红包
  3. android progressbar动态,如何在android中动态启动和完成progressBar
  4. 别等了,全面「远程办公」凉了
  5. 山西DotNet俱乐部网站改版成功
  6. C++学习之路 | PTA乙级—— 1042 字符统计 (20 分)(精简)
  7. Qt文档阅读笔记-共享库的创建与调用
  8. Spring拓展接口之FactoryBean
  9. python添加环境变量代码_Maya中的PYTHONPATH 环境变量
  10. 计算机管理制度上墙,机房日常管理制度(上墙
  11. python范围缩放_如何缩放到初始绘图/缩放的特定范围?
  12. html倒计时免费代码,JS倒计时代码汇总
  13. Spring 官方修复零日漏洞,推出 Spring Boot 2.6.6、2.5.12 等新版本
  14. MySQL如何统计表格的总行数
  15. 在PyCharm环境中使用graphviz遇到的问题
  16. 一文搞懂网卡驱动的原理与移植方法
  17. unzip 解压大文件出现错误invalid zip file with overlapped components (possible zip bomb)(linux分卷解压大文件错误解决)
  18. halcon拓展系列—求平面度、段差算子regions_to_plane
  19. Matlab生成sinc信号
  20. 区块链如何破解供应链金融痛点

热门文章

  1. 混日子?办公室惰性终结者-《让员工自发循环工作的管理体系》-捷径体系
  2. 支付宝资深技术专家尹博学:新一代金融核心突破之全分布式单元化技术架构...
  3. 图像变换——(DFT、DCT变换,IDFT、IDCT重建)
  4. Java数据库 Mysql和Idea环境
  5. 微信PC绿色版有什么特点
  6. 第1台电子计算机英文缩写名,1世界第一台电子计算机的英文名称是.docx
  7. Python爬虫之美丽的汤——BeautifulSoup
  8. Oracle 数据字典视图(V$,GV$,X$) #
  9. intel h61 linux驱动下载,佳能 Socket 1155 GA-H61M-DS2(Intel H61)驱动程序下载-更新佳能软件(母板)...
  10. 阿里问题定位神器 Arthas 的骚操作,定位线上BUG,超给力!