上文介绍了Zuul的基本使用与路由功能,本文接着介绍Zuul的核心概念 —— Zuul过滤器(filter)。

Zuul的功能基本通过Zuul过滤器来实现(类比于Struts的拦截器,只是Struts拦截器用到责任链模式,Zuul则是通过FilterProcessor来控制执行),在不同的阶段,通过不同类型的过滤器来实现相应的功能。

Zuul过滤器

过滤器类型

zuul的过滤器根据对HTTP请求的不同处理阶段包括如下四种类型

  • pre :在请求转发到后端目标服务之前执行,一般用于请求认证、确定路由地址、日志记录等
  • route :转发请求,使用Apache HttpClient 或 Ribbon来构造对目标服务的请求
  • post :在目标服务返回结果后对结果进行处理,比如添加响应头、收集统计性能数据等
  • error :在请求处理的整个流程中如果出现错误,则会触发error过滤器执行,对错误进行处理

客户端请求经过zuul过滤器处理的流程如下图

zuul使用RequestContext来在过滤器之间传递数据,数据存于每个request的ThreadLocal,包含请求路由到哪里,错误,HttpServletRequest,HttpServletResponse 等这些数据都存储于RequestContext中。RequestContext 扩展了ConcurrentHashMap,所以我们可以根据需要将信息存于context中进行传递。

@EnableZuulProxy vs @EnableZuulServer

zuul提供了两个注解 @EnableZuulProxy, @EnableZuulServer,来启用不同的过滤器集合。@EnableZuulProxy 启用的过滤器 是@EnableZuulServer 的超集, 它包含了@EnableZuulServer 的所有过滤器,proxy主要多了一些提供路由功能的过滤器(可见@EnableZuulServer 不提供路由功能,作为server模式而不是代理模式运行)

@EnableZuulServer 注解启用的过滤器包括

filter类型 实现类 filter顺序值 功能说明
pre ServletDetectionFilter -3 检测请求是否通过Spring Dispatcher,并在RequestContext 中添加一个key为isDispatcherServletRequest, 值为true(不通过则为false)的属性
pre FormBodyWrapperFilter -1 解析Form data,为请求的下游进行重新编码
pre DebugFilter 1 如果请求参数设置了debug,则会将RequestContext.setDebugRouting() ,RequestContext.setDebugRequest() 设置为ture
route SendForwardFilter 500 使用RequestDispatch servlet来转发请求,转发地址存于RequestContext中key为FilterConstants.FORWARD_TO_KEY的属性中,对于转发到当前应用的接口比较有用
post SendResponseFilter 1000 将代理请求的响应内容写到当前的响应中
error SendErrorFilter 0 如果RequestContext.getThrowable() 不为空,则会转发到/error,可以通过error.path来改变默认的转发路径/error

@EnableZuulProxy 除了上面的过滤器,还包含如下过滤器

filter类型 实现类 filter顺序值 功能说明
pre PreDecorationFilter 5 确定路由到哪里,如何路由,依赖提供的RouteLocator,同时也为下游请求设置多个与proxy相关的header
route RibbonRoutingFilter 10 使用ribbon,hystrix,以及内嵌的http client来发送请求,可在RequestContext中通过FilterConstants.SERVICE_ID_KEY 来找到路由Service的ID
route SimpleHostRoutingFilter 100 使用Apache httpClient来发送请求到一个预先确定的url,可通过RequestContext.getRouteHost()来获取urls

由上可见@EnableZuulServer 注解并不包含往后端服务负载均衡地路由请求的代理功能,@EnableZuulProxy的PreDecorationFilter,RibbonRoutingFilter过滤器才能担当此任。PreDecorationFilter通过提供的DiscoveryClientRouteLocator 从 DiscoveryClient(如Eureka)与属性文件中加载路由定义, 为每个serviceId创建一个route,新服务添加进来,路由也会动态刷新。路由确定了,在RibbonRoutingFilter 中通过ribbon与hystrix结合来向后端目标服务发起请求,并进行负载均衡。过滤器的顺序值表示在同类型过滤器中的执行顺序,值越小越先执行。

自定义Zuul过滤器

自定义的zuul过滤器与框架自带过滤器类似,包括四部分

  1. 过滤器类型,包括pre, route, post
  2. 过滤器顺序,定义在同类型过滤器中的执行顺序,数值越小越先执行
  3. 是否执行过滤,通过一些条件判断来确定是否执行该过滤器
  4. 过滤器执行体,定义具体执行的操作

比如我们需要在Http请求头中设置一个值,供请求链路的下游环节访问,则可以自定义一个过滤器如下,

@Component
public class ReqIdPreFilter extends ZuulFilter { @Override
public String filterType() {
return FilterConstants.PRE_TYPE;
} @Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1; //在PreDecorationFilter过滤器之前执行
} @Override
public boolean shouldFilter() {
return true;
} @Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("reqId", UUID.randomUUID().toString());
return null;
}
}

在请求的后续环节,比如后端服务的filter或接口中,则可直接从HttpServletRequest 获取该header值,如

@GetMapping("hello/reqId")
public String getReqId(HttpServletRequest request) {
return "hello-service返回:" + request.getHeader("reqId");
}

Zuul的错误处理

在zuul过滤器的生命周期中,如果任何一个环节抛出异常,则error过滤器会被执行,SendErrorFilter只有当RequestContext.getThrowable()不为null时才会运行,会设置javax.servlet.error.* 属性到request中,然后将请求转发到spring boot的error page, 默认为BasicErrorController实现的/error接口。 有时候我们需要将返回响应格式进行统一,而默认的/error接口实现可能不满足要求,则可以自定义/error接口。需要实现ErrorController 接口以使默认的BasicErrorController 失效。

@RestController
public class ZuulErrorController implements ErrorController { @RequestMapping("/error")
public Map<String, String> error(HttpServletRequest request){
Map<String, String> result = Maps.newHashMap();
result.put("code", request.getAttribute("javax.servlet.error.status_code").toString());
result.put("message", request.getAttribute("javax.servlet.error.message").toString());
result.put("exception", request.getAttribute("javax.servlet.error.exception").toString());
return result;
} @Override
public String getErrorPath() {
return "/error";
}
}

Zuul的服务降级

当调用服务出现超时或异常时,在zuul侧可提供回调进行服务降级,返回默认响应结果,如

@Component
public class MyFallbackProvider implements FallbackProvider { @Override
public String getRoute() {
return null; //指定这个回调针对的route Id,如果对所有route,则返回* 或null
} @Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
} private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
}
@Override
public int getRawStatusCode() throws IOException {
return status.value();
}
@Override
public String getStatusText() throws IOException {
return status.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
Map<String, String> result = Maps.newLinkedHashMap();
result.put("code", "" + status.value());
String msg = HttpStatus.GATEWAY_TIMEOUT == getStatusCode() ? "请求服务超时" : "服务器内部错误";
result.put("message", msg);
return new ByteArrayInputStream(new ObjectMapper().writeValueAsString(result).getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}

则当服务请求失败时,统一返回如下格式的响应

{
"code": "500",
"message": "服务器内部错误"
}

总结

本文主要对Zuul过滤器相关内容及自定义使用进行了介绍,同时对过滤器运行过程中异常的处理及服务调用失败的降级回调进行了简单说明。出于篇幅,开发过程中更具体的细节我们后续再继续探讨。

认真生活,快乐分享

欢迎关注微信公众号:空山新雨的技术空间

获取Spring Boot,Spring Cloud,Docker等系列技术文章

Spring Cloud(七):服务网关zuul过滤器的更多相关文章

  1. Spring Cloud Gateway 服务网关快速上手

    Spring Cloud Gateway 服务网关 API 主流网关有NGINX.ZUUL.Spring Cloud Gateway.Linkerd等:Spring Cloud Gateway构建于 ...

  2. 二十一、springcloud(七)服务网关zuul

    1.简介 Eureka用于服务的注册于发现,Feign支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散,Spring Cloud Config服务集群配置中心,在微服务架构中,后端 ...

  3. Spring Cloud内置的Zuul过滤器详解

    Spring Cloud默认为Zuul编写并启用了一些过滤器,这些过滤器有什么作用呢?我们不妨按照@EnableZuulServer.@EnableZuulProxy两个注解进行展开,相信大家对这两个 ...

  4. Spring Cloud (13) 服务网关-路由配置

    传统路由配置 所谓传统路由配置方式就是在不依赖于服务发现机制情况下,通过在配置文件中具体制定每个路由表达式与服务实例的映射关系来实现API网关对外部请求的路由.没有Eureka服务治理框架帮助的时候, ...

  5. Spring Cloud (12) 服务网关-基础

    通过前几篇介绍,已经可以构建一个简单的微服务架构了,如下图: 通过eureka实现服务注册中心以及服务注册发现,通过ribbon或feign实现服务的消费以及负载均衡,通过spring cloud c ...

  6. Spring Cloud (14) 服务网关-过滤器

    Spring Cloud Zuul作为网关所具备的最基本的功能:路由,还具备另外一个核心的功能:过滤器. 过滤器 通过Spring Cloud Zuul实现的路由功能,我们的微服务可以通过统一的API ...

  7. Spring Cloud Gateway服务网关

    原文:https://www.cnblogs.com/ityouknow/p/10141740.html Spring 官方最终还是按捺不住推出了自己的网关组件:Spring Cloud Gatewa ...

  8. spring cloud:服务网关 Spring Cloud GateWay 入门

    Spring 官方最终还是按捺不住推出了自己的网关组件:Spring Cloud Gateway ,相比之前我们使用的 Zuul(1.x) 它有哪些优势呢?Zuul(1.x) 基于 Servlet,使 ...

  9. Spring Cloud 之 服务网关

    在微服务架构体系中,使用API 服务网关后的系统架构图如下: API服务网关的主要作用如下: 服务访问的统一入口 服务访问的负载均衡功能 服务访问的路由功能 在SpringCloud中,基于Netfl ...

随机推荐

  1. Eclipse中安装Jetty服务器

    1. 在eclipse中安装jetty适配器 方法一: (1) 打开 Windows -> Preference -> Server -> Runtime Environment , ...

  2. NOIP提高组2018试题解析 Day1 T1 铺设道路 P5019

    题目描述 春春是一名道路工程师,负责铺设一条长度为 nn 的道路. 铺设道路的主要工作是填平下陷的地表.整段道路可以看作是 nn 块首尾相连的区域,一开始,第 ii 块区域下陷的深度为 d_idi​  ...

  3. hdu 5139 数据的离线处理

    所谓的数据离线处理,就是将所有的输入数据全部读入后,在进行统一的操作,这样当然有好处,比如让你算好多数的阶层,但是输入的每个数是没有顺序的,其实跟可以线性的解决,但是由于没有顺序的输入,这样处理的话复 ...

  4. Nginx作为静态web服务器——缓存原理

    浏览器缓存 ​ 客户端无缓存的情况下 ​ 客户端有缓存的情况下 ​ 校验过期机制 ​ 本地客户端会检查Cache-Control(max-age)缓存是否过期,(max-age)为过期时间 Last- ...

  5. Django 连接mysql 踩过的坑

    1.创建数据库 2.在Django项目文件下的settings.py配置数据库 3.在Django项目__init__.py文件中,用pymysql代替MySqlDB import pymysql p ...

  6. Java入门 - 语言基础 - 01.Java简介

    原文地址:http://www.work100.net/training/java-intro.html 更多教程:光束云 - 免费课程 Java简介 序号 文内章节 视频 1 概述 2 主要特性 3 ...

  7. kafka(一)-为什么选择kafka

    作为开发人员,我们在选择一个框架或者工具时,我们都需要考虑些什么,我们不是头脑发热,一拍脑袋就它了,我们首先要认清这个框架或工具的作用是什么,能给我们带来什么样的好处,同时也要考虑带来什么样的负面结果 ...

  8. [洛谷P3621] [APIO2007] 风铃

    Description 你准备给弟弟 Ike 买一件礼物,但是,Ike 挑选礼物的方式很特别:他只喜欢那些能被他排成有序形状的东西. 你准备给 Ike 买一个风铃.风铃是一种多层的装饰品,一般挂在天花 ...

  9. cssSelector定位写法大全(适用于selenium、robotframework)

    1.定位weibo登录框 输入框的元素信息如下 css的写法(可以看到name属性的属性值是“username”,class属性的值“W_input" driver.findElement( ...

  10. AVLtree(C++实现)有统一的旋转操作

    在学习完AVLtree之后,我发现,左旋,右旋均可以采用统一的旋转方式来实现,所以把代码贴在下面 代码是完整的AVLTree实现 C++标准为C++11 在ubuntu 18.04下通过编译和调试 / ...