SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

时间:2022-08-31 15:45:33

前言

在上篇中介绍了SpringCloud Zuul路由网关的基本使用版本,本篇则介绍基于SpringCloud(基于SpringBoot2.x,.SpringCloud Finchley版)中的路由网关的过滤器Filter以及异常处理的教程。

SpringCloud Zuul Filter

介绍

过滤器概述

Zuul的中心是一系列过滤器,能够在HTTP请求和响应的路由过程中执行一系列操作。

以下是Zuul过滤器的主要特征:

  • 类型:通常在应用过滤器时在路由流程中定义阶段(尽管它可以是任何自定义字符串)
  • 执行顺序:在类型中应用,定义跨多个过滤器的执行顺序
  • 标准:执行过滤器所需的条件
  • 操作:满足条件时要执行的操作
  • Zuul提供了一个动态读取,编译和运行这些过滤器的框架。过滤器不直接相互通信 - 而是通过RequestContext共享状态,RequestContext对每个请求都是唯一的。

过滤器目前用Groovy编写,尽管Zuul支持任何基于JVM的语言。每个Filter的源代码都写入Zuul服务器上的一组指定目录,这些目录会定期轮询更改。更新的过滤器从磁盘读取,动态编译到正在运行的服务器中,并由Zuul为每个后续请求调用。

过滤器类型与请求生命周期

Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。

  • PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  • ERROR:在其他阶段发生错误时执行该过滤器。

官网Wiki 提供的四种过滤器的生命周期图。

SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

:此段来之Netflix / zuul的官网Wiki,地址:https://github.com/Netflix/zuul/wiki/How-it-Works。

开发准备

开发环境

  • JDK:1.8
  • SpringBoot:2.0.6.RELEASE
  • SpringCloud:Finchley.SR2

注:不一定非要用上述的版本,可以根据情况进行相应的调整。需要注意的是SpringBoot2.x以后,jdk的版本必须是1.8以上!

服务端

由于在上一篇中我们已经完成了Zuul路由网关的基本功能实现,所以服务端这块我们可以直接把之前的项目拿来直接使用,然后更改相应的名称以及相关代码即可。

自定义过滤器

这里我们来编写自定义一个Filter实现类,看看该类是如何工作的。

在编写该类的时候,发现自定义的Filter类需要继承ZuulFilter这个类,我们查看该类的源码,发现了该类定义了两个抽象的方法,并且该类实现了IZuulFilter该接口,该接口也定义了两个方法,我们就来看看这几个方法到底是干嘛的吧。

ZuulFilter源码:

SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

filterType这个方法表示按类型对过滤器进行分类,分别是pre、post、route和error,在FilterConstants这个常量类中已经进行定义了,其意义在上述的Filter请求的典型生命周期已经进行过说明了。

filterOrder 这个方法表示Filter执行的顺序,数值越小优先级越高。

IZuulFilter

SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

shouldFilter该方法表示是否执行该过滤器,也可以说是该过滤器的一个开关。

run过滤器的具体逻辑。在该函数中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,或是在请求路由返回结果之后,对处理结果做一些加工等。

看完上述的源码之后,这里我们再来编写自定的Filter代码。

首先继承ZuulFilter该类,然后实现里面的方法。

首先是shouldFilter方法,这里我们就直接返回true;

然后是filterType,这里我们就设置为前置过滤器,在请求被路由之前调用。

继而是filterOrder,这里我们就设置0;

最后是run,这是过滤器的核心业务代码,这里我们就简单一点,获取请求的url,如果该url携带了token,我们就让他通过,否则直接拦截。

当然,我们需要将该过滤类使用Bean注解使其生效。

那么代码如下:

自定义的Filter代码:

@Component
public class MyZuulFilter extends ZuulFilter{ @Override
public boolean shouldFilter() {
return true;
} @Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
ctx.addZuulResponseHeader("Content-type", "text/json;charset=UTF-8");
ctx.getResponse().setCharacterEncoding("UTF-8");
System.out.println("请求地址:"+request.getRequestURI());
String token = request.getParameter("token");
String msg="请求成功!";
if(token==null) {
ctx.setSendZuulResponse(false);
msg="请求失败!";
ctx.setResponseBody(msg);
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return msg;
} @Override
public String filterType() {
return FilterConstants.PRE_TYPE;
} @Override
public int filterOrder() {
return 0;
} @Bean
public MyZuulFilter zuulFilter() {
return new MyZuulFilter();
}
}

自定义异常类处理

Zuul除了可以自定义过滤器之外,也可以对异常结果进行处理,以保持返回值一致。在进行Zuul使用的时候发现了在发生了异常之后,会调用SendErrorFilter异常过滤器,对异常经常处理,同时重定向至/error这个路径中。所以如果我们需要自定义对异常处理的话,继承SendErrorFilter该类就可以实现了。我们查看SendErrorFilter源码,其实也是继承ZuulFilter该类并实现里面的一些方法,做的自定义异常封装,其实也可以把SendErrorFilter该类当做一个自定义的过滤器。

由于SendErrorFilter是对ZuulFilter类进行了二次封装,所以我们自定义的Error代码只需继承SendErrorFilter改成,然后实现其中的run方法即可。

自定义的Error代码:

@Component
public class MyErrorFilter extends SendErrorFilter{ @Override
public Object run() {
String msg="请求失败!";
try{
RequestContext ctx = RequestContext.getCurrentContext();
ExceptionHolder exception = findZuulException(ctx.getThrowable());
System.out.println("错误信息:"+exception.getErrorCause());
msg+="error:"+exception.getErrorCause();
HttpServletResponse response = ctx.getResponse();
response.setCharacterEncoding("UTF-8");
response.getOutputStream().write(msg.getBytes());
}catch (Exception ex) {
ex.printStackTrace();
ReflectionUtils.rethrowRuntimeException(ex);
}
return msg;
} @Bean
public MyErrorFilter errorFilter() {
return new MyErrorFilter();
}
}

这里我们还需要禁用SendErrorFilter过滤器,不然是不会使用我们自定的异常过滤器的。

application.properties 添加如下配置:

zuul.SendErrorFilter.error.disable=true

这里顺便说下禁用过滤器的规则。组件实现的过滤器,满足执行条件时都是会执行的,若我们想禁用某个过滤器时,可以在配置文件中配置。

规则:

zuul.<SimpleClassName>.<filterType>.disable=true

说明:

SimpleClassName为类名,filterType过滤器类型

当然,如果觉得上述的异常处理还是不够优雅的话,可以使用ControllerAdvice注解进行全局异常处理,该注解的使用示例可以从个人的springboot项目中进行找到,地址:https://github.com/xuwujing/springBoot-study

自定义异常回退处理

在之前的关于springcloud中SpringCloud学习系列之三----- 断路器(Hystrix)和断路器监控(Dashboard)这篇文章中讲解过服务的降级处理,其实这里的处理也是类似,也就是某个服务无法进行访问的时候,进行回退处理。

这里我们自定义异常回退处理的代码相对而已也比较简单,只需实现FallbackProvider该接口的方法既可。

该类的源码如下:

SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

getRoute该方法主要是指定需要回退服务的名称。

fallbackResponse该方法提供基于执行失败原因并进行回退响应。

了解之后该源码之后,我们再来编写一个自定义异常回退处理的类。

自定义的Fallback代码:


@Component
public class MyFallback implements FallbackProvider {
private static final String SERVER_NAME="springcloud-zuul-filter-server2"; @Override
public String getRoute() {
return SERVER_NAME;
} @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) {
String msg="该"+SERVER_NAME+"服务暂时不可用!";
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 {
return new ByteArrayInputStream(msg.getBytes());
} @Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
} @Bean
public MyFallback eurekaClientFallback() {
return new MyFallback();
}
}

客户端

客户端这边,我们可以把之前springcloud-zuul项目中的springcloud-zuul-server1springcloud-zuul-server2拿来进行使用既可。

测试

完成上述的代码开发后,我们来进行springcloud-zuul的一系列自定义过滤测试。

首先依次启动springcloud-zuul-filter-eurekaspringcloud-zuul-filter-gatewayspringcloud-zuul-filter-server1springcloud-zuul-filter-server2这四个项目。其中9009是服务端springcloud-zuul-filter-gatewayr的端口,9010是第一个客户端springcloud-zuul-filter-server1的端口,9011是第二个客户端springcloud-zuul-filter-server2的端口。

这里顺便说下路由网关的默认规则:http://ZUUL_HOST:ZUUL_PORT/微服务实例名(serverId)/** ,转发至serviceId对应的微服务。比如在浏览器输入:http://localhost:9009/springcloud-zuul-filter-server1/hello地址, 它就会跳转访问到:http://localhost:9010/hello/这个地址上。使用这个方式进行测试可以帮助我们更好的了解本篇文章的实现目的。

自定义过滤器功能测试

完成上述的项目启动成功之后。

我们首先在浏览器上输入:

http://localhost:9009/springcloud-zuul-filter-server1/hello?name=pancm

界面返回:

请求失败!

SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

这里看到直接进行拦截了,并返回了相应的信息、

加上token之后在进行访问

http://localhost:9009/springcloud-zuul-filter-server1/hello?name=pancm&token=123

界面返回:

pancm,Hello World!

SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

我们按照我们自定的规则进行访问之后,发现可以直接访问到我们想要访问的服务上,因此该次测试也符合我们的预期,达成了自定义过滤器的处理。

自定义异常类处理功能测试

上述的功能测试ok之后,这里我们停止掉springcloud-zuul-filter-server1服务,然后在浏览器上输入:

http://localhost:9009/springcloud-zuul-filter-server1/hello?name=pancm&token=123

界面返回:

请求失败!error:GENERAL请求失败!error:GENERAL

SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

注: 这里实际是调用了两次。

可以看到这次测试也符合我们的预期,达成了自定义异常的处理。

自定义异常回退处理功能测试

这里我们再来停止掉springcloud-zuul-filter-server2服务,然后在浏览器上输入:

http://localhost:9009/springcloud-zuul-filter-server2/hi?name=pancm&token=123

界面返回:

该springcloud-zuul-filter-server2服务暂时不可用!

SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

可以看到这次测试也符合我们的预期,达成了 自定义异常回退处理的处理。这里也顺便说下,自定义该服务的异常和自定义异常回退处理最好不要在同一个服务同时使用,如果同时使用,会优先进行自定义异常回退处理的处理。

其他

参考:

https://github.com/Netflix/zuul/wiki/How-it-Works

https://cloud.spring.io/spring-cloud-static/Finchley.SR1/single/spring-cloud.html#_router_and_filter_zuul

http://www.itmuch.com/spring-cloud/zuul/spring-cloud-zuul-filter/

https://blog.lqdev.cn/2018/10/17/SpringCloud/chapter-ten/#参考资料

项目地址

基于SpringBoot2.x、SpringCloud的Finchley版本开发的地址:https://github.com/xuwujing/springcloud-study

如果感觉项目不错,希望能给个star,谢谢!

springcloud系列博客:

音乐推荐

原创不易,如果感觉不错,希望留言推荐!您的支持是我写作的最大动力!

版权声明:

作者:虚无境

博客园出处:http://www.cnblogs.com/xuwujing

CSDN出处:http://blog.csdn.net/qazwsxpcm    

个人博客出处:http://www.panchengming.com

SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理的更多相关文章

  1. SpringCloud与微服务Ⅸ --- Zuul路由网关

    一.Zool是什么 Zuul包含了对请求路由和过滤两个最主要的功能: 其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现 ...

  2. SpringCloud学习(五)路由网关&lpar;zuul&rpar;&lpar;Finchley版本&rpar;

    在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现.服务消费.负载均衡.断路器.智能路由.配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统.一个简单的微服务系统如下图: ...

  3. springcloud 入门 7 (zuul路由网关)

    Zuul简介: Zuul的主要功能是路由转发和过滤器.路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务.zuul默认和Ribbon结合实现了 ...

  4. SpringCloud学习系列之六 ----- 路由网关Zuul基础使用教程

    前言 在上篇中介绍了SpringCloud Config的完美使用版本,本篇则介绍基于SpringCloud(基于SpringBoot2.x,.SpringCloud Finchley版)中的路由网关 ...

  5. SpringCloud的入门学习之概念理解、Zuul路由网关

    1.Zuul路由网关是什么? 答:Zuul包含了对请求的路由和过滤两个最主要的功能,其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进 ...

  6. SpringCloud学习笔记(八):Zuul路由网关

    概述 是什么? Zuul包含了对请求的路由和过滤两个最主要的功能: 其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请 ...

  7. 白话SpringCloud &vert; 第十一章:路由网关&lpar;Zuul&rpar;:利用swagger2聚合API文档

    前言 通过之前的两篇文章,可以简单的搭建一个路由网关了.而我们知道,现在都奉行前后端分离开发,前后端开发的沟通成本就增加了,所以一般上我们都是通过swagger进行api文档生成的.现在由于使用了统一 ...

  8. SpringCloud 进阶之Zuul&lpar;路由网关&rpar;

    1. Zuul(路由网关) Zuul 包含了对请求的路由和过滤两个最主要的功能; 路由功能:负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础; 过滤功能:负责对请求的处理过程进行干 ...

  9. 【七】zuul路由网关

    一.zuul是什么?zuul 包含以下两个最主要的功能:1.路由功能: 负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础.2.过滤器功能: 则负责对请求的处理过程进行干预,是实现请 ...

随机推荐

  1. NVelocity

    迭代内置对象:  velocityCount 集合数   :  count NVelocity遇到不能处理的引用时,一般会直接输出标签名称. 在$符号后加个!号,出现Null时,标签的内容就会显示空白 ...

  2. Application&lowbar;Error VS OnException 遇到的坑

    在工作中遇到一个巨坑,就是关于Application_Error和OnException,            本身我的应用程序设置了全局异常OnException处理,手动抛出异常,OnExcep ...

  3. SQL Server 阻止了对组件 &&num;39&semi;Ad Hoc Distributed Queries&&num;39&semi; 的 STATEMENT&&num;39&semi;OpenRowset&sol;OpenDatasource&&num;39&semi; 的访问,因为此组件已作为此服务器安全配置的一部分而被关闭。系统管理员可以通过使用 sp&lowbar;configure 启用 &&num;39&semi;Ad Hoc Distributed Queries&&num;39&semi;。有关启用 &&num;39&semi;Ad Hoc Distributed Que

    看错误提示就知道是因为SQL Server的Ad Hoc Distributed Queries组件被禁用了,这里我用的SQL Server版本是2005,只需要开启Ad Hoc Distribute ...

  4. c&plus;&plus; 程序在内存中的分布

    从低地址到高地址: 1.代码区[包含常量的]:存放函数体的二进制代码 2.全局变量区[已初始化 + 未初始化]: 全局变量和静态变量的存储是放一块的,初始化的全局变量和静态变量在一块区域, 未初始化的 ...

  5. andrid中的Sqlite 数据库连接(本地版)

    sqlite简介 SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中.它是D.RichardHipp建立的公有领域项目.它的设计目标是嵌入式的,而且目前 ...

  6. Android面试经验2

    1,android如何更换主题: 2,如何设计软件: 3,代码中用到那些设计模式: 4,c++和java有那些不同: 一,指针: 二,多重继承: 三,数据类型和类: 四,自动内存管理: 五,操作符重载 ...

  7. 第四十篇-private&comma;public&comma;protected的区别

    1.public: public表明该数据成员.成员函数是对所有用户开放的,所有用户都可以直接进行调用 2.private: private表示私有,私有的意思就是除了class自己之外,任何人都不可 ...

  8. MVC Post 提交表单 允许他提交参数包含html标记的解决方法

    MVC Post 提交表单的时候,如果参数中包含html标记,则需要在控制器上方加上 [ValidateInput(false)]标记后就可以正常提交表单了例如: [HttpPost] [Valida ...

  9. 接触Java23天

    根据老师的要求写了一段然后在评讲的时候在修该一些: 猫的: public class Cat extends Animal{ public void methodCat(){ System.out.p ...

  10. hdu 4970 trick

    http://acm.hdu.edu.cn/showproblem.php?pid=4970 有n个格子在一条线标号1-n上,可以给范围在l到r内的格子架上攻击力为d的攻击塔,有m个怪物,每个怪物有个 ...