白话SpringCloud | 第十章:路由网关(Zuul)进阶:过滤器、异常处理
前言
简单介绍了关于
Zuul
的一些简单使用以及一些路由规则的简单说明。而对于一个统一网关而言,需要处理各种各类的请求,对不同的url进行拦截,或者对调用服务的异常进行二次处理等等。今天,我们就来了解下这方面的相关知识点。
一点知识
开始实践前,我们先来了解下Zuul
默认的过滤器(注意,这里讲解的Zuul
都是1.X
版本的)。上一章节,也提到了Zuul
的核心就是一系列过滤器。现在我们来看看Zuul
的过滤器相关信息。
过滤器的定义
Zuul
中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。
- PRE:可以在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- ROUTING:在路由请求时候被调用。这种过滤器用于构建发送给微服务的请求,并使用
Apache HttpClient
或Netfilx Ribbon
请求微服务。 - POST:在
routing
和error
过滤器之后被调用。这种过滤器可用来为响应添加标准的HTTP Header
、收集统计信息和指标、将响应从微服务发送给客户端等。 - ERROR:处理请求时发生错误时被调用。
现在看下官网wiki提供的四种过滤器的生命周期图。
一个请求会先按顺序通过所有的前置过滤器,之后在路由过滤器中转发给后端应用,得到响应后又会通过所有的后置过滤器,最后响应给客户端。在整个流程中如果发生了异常则会跳转到错误过滤器中。
一般来说,如果需要在请求到达后端应用前就进行处理的话,会选择pre(前置过滤器)
,例如鉴权、请求转发、增加请求参数等行为。在请求完成后需要处理的操作放在(post)后置过滤器
中完成,例如统计返回值和调用时间、记录日志、增加跨域头等行为。路由过滤器一般只需要选择 Zuul 中内置的即可,错误过滤器一般只需要一个,这样可以在遇到错误逻辑时直接抛出异常中断流程,并直接统一处理返回结果
说下error
过滤器:pre
、routing
的任意一个阶段如果抛异常了,则执行error
过滤器,然后再执行post
给出响应。而post
异常了,就直接调用error
了。
过滤器接口定义
知道了过滤器的定义,我们看看过滤器是怎么被定义的。查看类com.netflix.zuul.ZuulFilter
类,可知其个抽象类:
以下为需要实现的方法,其他具体的可自行查阅下
//过滤器类型
String filterType();
//执行顺序 越小越先执行
int filterOrder();
//是否执行 返回false 不执行此过滤器
boolean shouldFilter();
//过滤器执行逻辑
Object run();
具体说明下:
- filterType:该函数需要返回一个字符串来代表过滤器的类型,而这个类型就是在HTTP请求过程中定义的各个阶段。在Zuul中默认定义了四种不同生命周期的过滤器类型,具体如下:
- pre:可以在请求被路由之前调用。
- routing:在路由请求时候被调用。
- post:在routing和error过滤器之后被调用。
- error:处理请求时发生错误时被调用。
- filterOrder:通过int值来定义过滤器的执行顺序,数值越小优先级越高。
- shouldFilter:返回一个
boolean
类型来判断该过滤器是否要执行。我们可以通过此方法来指定过滤器的有效范围。 - run:过滤器的具体逻辑。在该函数中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,或是在请求路由返回结果之后,对处理结果做一些加工等。
所以,了解了过滤器抽象类的定义,自定义抽象类就简单了。
zuul自带过滤器
通过IDE
我们来看下已经实现ZuulFilter
的过滤器类。具体的类在:
看看已经提供的过滤器:
可以看见,Spring cloud zuul
提供了很多过滤器,基本上就开箱即用了。简单说明下:
类型 | 顺序 | 过滤器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 标记处理Servlet的类型 |
pre | -2 | Servlet30WrapperFilter | 包装HttpServletRequest请求 |
pre | -1 | FormBodyWrapperFilter | 包装请求体 |
pre | 1 | DebugFilter | 标记调试标志 |
pre | 5 | PreDecorationFilter | 处理请求上下文供后续使用 |
route | 10 | RibbonRoutingFilter | serviceId请求转发 |
route | 100 | SimpleHostRoutingFilter | url请求转发 |
route | 500 | SendForwardFilter | forward请求转发 |
error | 0 | SendErrorFilter | 处理有错误的请求响应 |
post | 1000 | SendResponseFilter | 处理正常的请求响应 |
禁用过滤器
组件实现的过滤器,满足执行条件时都是会执行的,若我们想禁用某个过滤器时,可以在配置文件中配置。
规则:zuul.<SimpleClassName>.<filterType>.disable=true
说明:SimpleClassName为类名,filterType过滤器类型
#禁用DebugFilter过滤器
zuul.DebugFilter.pre.disable=true
Zuul进阶示例
为了区分不混淆,创建一个新的项目进行示例:spring-cloud-zuul-advanced
。
对于通用部分,如pom依赖等都是和项目spring-cloud-zuul
一样的,不一样的会具体指出的。大家可查看《第九章:路由网关(Zuul)的使用》,这里就不重复贴了。
自定义filter
通过以上几个小节的说明,我们通过继承ZuulFilter
类进行自定义过滤器的编写。这里直接校验请求的参数是否带有token
,若无此参数时,直接进行请求拦截。
/**
* 自定义过滤器-校验请求参数是否合法:包含token参数
* @author oKong
*
*/
@Slf4j
public class AccessZuulFilter extends ZuulFilter{
@Override
public boolean shouldFilter() {
//此方法可以根据请求的url进行判断是否需要拦截
return true;
}
@Override
public Object run() throws ZuulException {
//获取请求的上下文类 注意是:com.netflix.zuul.context包下的
RequestContext ctx = RequestContext.getCurrentContext();
//获取request对象
HttpServletRequest request = ctx.getRequest();
//避免中文乱码
ctx.addZuulResponseHeader("Content-type", "text/json;charset=UTF-8");
ctx.getResponse().setCharacterEncoding("UTF-8");
//打印日志
log.info("请求方式:{},地址:{}", request.getMethod(),request.getRequestURI());
String token = request.getParameter("token");
if(StringUtils.isBlank(token)) {
//使其不进行转发 自定义route类型时,在shouldFilter中也需要进行此参数判断。
ctx.setSendZuulResponse(false);
ctx.setResponseBody("{\"code\":\"999500\",\"msg\":\"非法访问\"}");
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());//401
//或者添加一个额外参数也可以 传递参数可以使用
// ctx.set("checkAuth",false);
}
//这返回值没啥用
return null;
}
@Override
public String filterType() {
//前置过滤器
return PRE_TYPE;
}
@Override
public int filterOrder() {
//执行顺序 0 靠前执行 在spring cloud zuul提供的pre过滤器之后执行,默认的是小于0的。
//除了参数校验类的过滤器 一般上直接放在 PreDecoration前
//即:PRE_DECORATION_FILTER_ORDER - 1;
//常量类都在:org.springframework.cloud.netflix.zuul.filters.support.FilterConstants 下
return 0;
}
}
同时在启动类中使用@Bean
标记,使其生效。
@Bean
public AccessZuulFilter accessZuulFilter() {
return new AccessZuulFilter();
}
注意:Spring cloud
为我们提供了常量类:org.springframework.cloud.netflix.zuul.filters.support.FilterConstants
静态引入对于的常量即可。里面包含了各过滤器的执行顺序值、过滤器类型常量以及一些头部参数或者变量参数名:请求服务ID
、请求URI
等。这些参数都是很有用的,比如请求服务ID
,若为空,则直接使用SimpleHostRoutingFilter
进行请求转发,否则是RibbonRoutingFilter
进行服务转发。这些变量都是通过PreDecorationFilter
前置过滤器进行赋值处理的。
启动应用,访问:http://127.0.0.1:8889/myapi/hello?name=oKong 可以看见,请求被拦截了,返回了非法访问提示。
接着,我们请求参数带上token
:http://127.0.0.1:8889/myapi/hello?name=oKong&token=okong ,可以看见请求被正常转发了。
异常处理
从目前的文件中,我们可以知晓:目前可以通过serviceId
、url
进行请求转发,根据PreDecorationFilter
前置过滤器鉴别不同的类型,最后通过ribbon
或者常规的http
访问目标服务。在访问目标服务,发生异常是在正常不过的了。从第一小节我们可以获悉,当过滤器发生异常时,会调用error
过滤器进行异常信息处理,默认情况下就是:SendErrorFilter
。首先,我们看看,默认情况下,以上两种异常是如何进行异常信息展现的。
首先,我们spring-cloud-eureka-client
服务停止了,之后访问下:http://127.0.0.1:8889/eureka/hello?name=oKong&token=okong ,可以看见返回的就是正常boot
默认异常,即:/error
页面。
接着,访问下:http://127.0.0.1:8889/myapi/hello?name=oKong&token=okong ,相同的都是跳转至/error
页面。
可以发现,第二种错误信息更加直观也更有用,可以获悉是服务不可用造成的。
现在,我们来看看,SendErrorFilter
类的run
方法。
可以获悉,其主要的生效条件是包含异常对象:throwable
,而第二个条件只是为了避免二次执行。为了了解下其调用关系,我们查看下com.netflix.zuul.http.ZuulServlet
类的service
方法,这个类它定义了Zuul处理外部请求过程时,各个类型过滤器的执行逻辑。
以上截图了此类的service
方法,可以看见,每调用一个过滤器类型时,外部都是用try..catch
包裹了,异常发生时都调用了error
方法,现在我们看看error()
方法。
可以看见,当一个触发器发生异常时,统一设置了异常对象throwable
,而后去调用error
类型的过滤器。
针对网关自己的api
接口时,和普通的web
应用是一样的了。也是跳转至/error
上,此时可以使用@ControllerAdvice
进行统一异常处理。关于统一异常的处理,可以查看《SpringBoot | 第八章:统一异常、数据校验处理》,这里就不阐述了。
服务异常回退
通过前一章节,我们值得可以通过注册中心的服务ID进行自动转发,当远程服务不可用时,我们可以通过Hystrix
进行服务回退处理。官网文档也说明了,只需实现FallbackProvider
接口类即可。
创建一个服务eureka-client
的异常回退类:myEurekaClientFallback
。
/**
* 服务 eureka-client 的异常退回处理类
* @author oKong
*/
public class MyEurekaClientFallback implements FallbackProvider {
@Override
public String getRoute() {
// TODO Auto-generated method stub
return "eureka-client";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
//标记不同的异常为不同的http状态值
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 {
//可替换成相应的json串的 看业务规定了
return new ByteArrayInputStream("{\"code\":\"999999\",\"msg\":\"服务暂时不可用\"}".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
同时在启动类中使用@Bean
标记,使其生效。
@Bean
public MyEurekaClientFallback eurekaClientFallback() {
return new MyEurekaClientFallback();
}
此时,我们停止spring-cloud-eureka-client
服务,访问:http://127.0.0.1:8889/eureka/hello?name=oKong&token=okong ,可以看见看见已经正确返回错误信息了。
另外,需要细化异常的,可对fallbackResponse
的Throwable
进行异常判断的,以获取具体的异常信息,如超时、处理异常等等。而且,设置了服务回退,此时对于route
过滤器而言是正常调用,未发生异常,所以也就不会调用error
过滤器了。
常规http请求异常
当使用Ribbon
进行服务调用时,我们可以使用FallbackProvider
进行调用,而当我们常规的使用url
进行转发时,我们也应该进行异常结果处理,以保持返回值一致。已经知道,发生异常时,会调用SendErrorFilter
异常过滤器,对异常经常处理,同时重定向至/error
中,所以,一般上我们可以自定义ErrorController类或者参照SendErrorFilter进行二次开发,对返回值进行个性化处理即可。这里简单演示下通过自定义异常过滤器
进行异常处理。
/**
* 自定义异常类 过滤器 直接扩展 SendErrorFilter 类
* @author oKong
*
*/
@Slf4j
public class CustomErrorFilter extends SendErrorFilter{
@Override
public Object run() {
//重写 run方法
try{
RequestContext ctx = RequestContext.getCurrentContext();
//直接复用异常处理类
ExceptionHolder exception = findZuulException(ctx.getThrowable());
log.info("异常信息:{}", exception.getThrowable());
//这里可对不同异常返回不同的错误码
HttpServletResponse response = ctx.getResponse();
response.getOutputStream().write(("{\"code\":\"999999\",\"msg\":\"" + exception.getErrorCause() + "\"}").getBytes());
}catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
}
同时,禁用SendErrorFilter
过滤器。
## 停用默认的异常处理器SendErrorFilter
zuul.SendErrorFilter.error.disable=true
在启动类,使用@Bean
生效自定义过滤器。
@Bean
@ConditionalOnProperty(name="zuul.SendErrorFilter.error.disable")
public CustomErrorFilter customErrorFilter() {
return new CustomErrorFilter();
}
重启应用,访问:http://127.0.0.1:8889/myapi/hello?name=oKong&token=okong ,可以看见已经是按自定义返回值返回了。
另外注意的是,前面也有提到,当访问不存在的路径或者转发路径时,依旧是普通的异常,可通过统一异常进行拦截,返回值拼装的。
参考资料
总结
本章节主要介绍了关于
Zuul
过滤器和相关异常处理的相关知识点。可能还是存在不完整的情况,大家在遇见相关问题时,可查阅下官方文档的。Zuul
本身还有一些其他的高级功能的,本人也用的不多,相关配置也是看了官方文档时才知道如何配置和使用的。所以,不知道相关配置时,可以去查阅下相关文档,比如一些忽略头部信息、忽略服务等等配置,都未涉及。主要还是用的不多。。原来我们都是自建一个restful
服务进行统一网关调用的,当频繁修改api时此方法就有点麻烦需要多次变动了。主要看业务需求吧,这东西可大可小的。最简单当然创建个简单的web
就行了,而当需要实现一些高级功能,比如灰度发布,动态引流时可能就需要考虑下使用Zuul
或者gateway
。有时间去看看gateway
,据说性能好呀。关于网关的暂时就告一段落了,接下来会分享一些服务之间调用异常处理的,敬请期待~
最后
目前互联网上大佬都有分享
SpringCloud
系列教程,内容可能会类似,望多多包涵了。原创不易,码字不易,还希望大家多多支持。若文中有错误之处,还望提出,谢谢。
老生常谈
- 个人QQ:
499452441
- 微信公众号:
lqdevOps
个人博客:http://blog.lqdev.cn
源码示例:https://github.com/xie19900123/spring-cloud-learning
原文地址:http://blog.lqdev.cn/2018/10/17/SpringCloud/chapter-ten/
白话SpringCloud | 第十章:路由网关(Zuul)进阶:过滤器、异常处理的更多相关文章
- 白话SpringCloud | 第十一章:路由网关(Zuul):利用swagger2聚合API文档
前言 通过之前的两篇文章,可以简单的搭建一个路由网关了.而我们知道,现在都奉行前后端分离开发,前后端开发的沟通成本就增加了,所以一般上我们都是通过swagger进行api文档生成的.现在由于使用了统一 ...
- 玩转SpringCloud(F版本) 四.路由网关(zuul)
本篇文章基于: 01)玩转SpringCloud 一.服务的注册与发现(Eureka) 02) 玩转SpringCloud 二.服务消费者(1)ribbon+restTemplate 03) 玩转Sp ...
- springcloud(十一):服务网关Zuul高级篇
时间过的很快,写springcloud(十):服务网关zuul初级篇还在半年前,现在已经是2018年了,我们继续探讨Zuul更高级的使用方式. 上篇文章主要介绍了Zuul网关使用模式,以及自动转发机制 ...
- Spring Cloud(十一):服务网关 Zuul(过滤器)【Finchley 版】
Spring Cloud(十一):服务网关 Zuul(过滤器)[Finchley 版] 发表于 2018-04-23 | 更新于 2018-05-07 | 在上篇文章中我们了解了 Spring ...
- 白话SpringCloud | 第九章:路由网关(Zuul)的使用
前言 介绍完分布式配置中心,结合前面的文章.我们已经有了一个微服务的框架了,可以对外提供api接口服务了.但现在试想一下,在微服务框架中,每个对外服务都是独立部署的,对外的api或者服务地址都不是不尽 ...
- SpringCloud学习系列之六 ----- 路由网关Zuul基础使用教程
前言 在上篇中介绍了SpringCloud Config的完美使用版本,本篇则介绍基于SpringCloud(基于SpringBoot2.x,.SpringCloud Finchley版)中的路由网关 ...
- springCloud之路API路由网关Zuul
1.简介 简单的理解就是,相当于在所有服务的调用前加了一层防火墙, 主要就是对外提供服务接口的时候,起到了请求的路由和过滤作用,也因此能够隐藏内部服务的接口细节,提高系统的安全性: 官方文档:http ...
- 路由网关---zuul
Zuul:Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架.Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门. 在微服务盛行的时代,客户端与系统之 ...
- 「 从0到1学习微服务SpringCloud 」10 服务网关Zuul
系列文章(更新ing): 「 从0到1学习微服务SpringCloud 」06 统一配置中心Spring Cloud Config 「 从0到1学习微服务SpringCloud 」07 RabbitM ...
随机推荐
- ecliplse集成反编译插件
言语不清晰,上图最直接 搜索框输入:Decompiler 等待安装: 重启之后进入下面的界面: 8.对反编译器( Decompiler )进行配置 二.配置说明 1.缺省类反编译器(Default C ...
- strcmp返回值布尔类型的判断
strcmp: 用于比较两个字符串,原型如下: int strcmp ( char const *s1, char const *s2):如果s1小于s2,strcmp函数返回一个小于零的值.如果s1 ...
- Linux虚拟机安装 nginx (nginx1.9.9)
1.安装基础环境包(如果已安装,可更新) yum -y :自动选择y yum -y install openssl* yum -y install libjpeg libjpeg-devel libp ...
- NSCalendar日历
前言 NSCalendar 对世界上现存的常用的历法进行了封装,既提供了不同历法的时间信息,又支持日历的计算. NSCalendar -- 日历类,它提供了大部分的日期计算接口,并且允许您在NSDat ...
- Mybatis 的动态 SQL 语句
<if>标签 我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询. 比如在 id 如果不为空时可以根据 id 查询, 如果 username 不同空时还要加入用户名作为条件.这种 ...
- docker容器管理及网络管理
防火墙规则—— INPUT 主要用于主机防火墙,设置规则屏蔽处理进入本机的数据包示例:禁止10.180.100.141这个机器访问我本机的web服务iptables -t filter -A INPU ...
- [Swift]八大排序算法(五):插入排序
排序分为内部排序和外部排序. 内部排序:是指待排序列完全存放在内存中所进行的排序过程,适合不太大的元素序列. 外部排序:指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存 ...
- mysql的唯一索引UNIQUE
创建唯一索引的目的不是为了提高访问速度,而只是为了避免数据出现重复.唯一索引可以有多个但索引列的值必须唯一,索引列的值允许有空值.如果能确定某个数据列将只包含彼此各不相同的值,在为这个数据列创建索引的 ...
- HDU6312 Game(博弈,拿出本数与这个数的除数)
题意:A和B玩游戏 , 给出1 ~ n 的集合 ,每个人可以拿出一个数 , 这个数的除数也被拿出 , A先开始 , 没有数拿的人就输 , 问A赢不赢 分析:很有意思的一道题目 ///假设2 ~ n A ...
- 最小生成树----prim算法的堆优化
题目描述 如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz 输入输出格式 输入格式: 第一行包含两个整数N.M,表示该图共有N个结点和M条无向边.(N<=5000,M<= ...