Spring Cloud(七):服务网关zuul过滤器

上文介绍了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等系列技术文章

原文地址:https://www.cnblogs.com/spec-dog/p/12340902.html

时间: 2024-07-29 21:18:23

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

Spring Cloud Gateway服务网关

原文:https://www.cnblogs.com/ityouknow/p/10141740.html Spring 官方最终还是按捺不住推出了自己的网关组件:Spring Cloud Gateway ,相比之前我们使用的 Zuul(1.x) 它有哪些优势呢?Zuul(1.x) 基于 Servlet,使用阻塞 API,它不支持任何长连接,如 WebSockets,Spring Cloud Gateway 使用非阻塞 API,支持 WebSockets,支持限流等新特性. Spring Clou

spring cloud 学习笔记--网关zuul学习

微服务是多个服务共同完成一件事情,那么"一致对外"就很有必要,就像我们去买面包,不可能先去找农民买小麦,再.... 盗图 spring cloud 引入zuul方式来实现这一功能 添加依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </depend

Spring Cloud(十一):服务网关 Zuul(过滤器)【Finchley 版】

Spring Cloud(十一):服务网关 Zuul(过滤器)[Finchley 版] 发表于 2018-04-23 |  更新于 2018-05-07 | 在上篇文章中我们了解了 Spring Cloud Zuul 作为网关所具备的最基本功能:路由(Router).本文我们将关注 Spring Cloud Zuul 的另一核心功能:过滤器(Filter). Filter 的作用 我们已经能够实现请求的路由功能,所以我们的微服务应用提供的接口就可以通过统一的 API 网关入口被客户端访问到了.但

Spring Cloud微服务中网关服务是如何实现的?(Zuul篇)

导读 我们知道在基于Spring Cloud的微服务体系中,各个微服务除了在内部提供服务外,有些服务接口还需要直接提供给客户端,如Andirod.IOS.H5等等. 而一个很尴尬的境地是,如果直接将提供外部接口的微服务暴露给公网,那么意味着为了增强这个微服务的安全性,需要做很多额外的安全性措施,如报文数字签名.加密等:而大部分场景下,微服务本身又是提供给内部其他微服务调用的,即便所有的微服务都会不同程度地直接面向App客户端提供公网服务,那么为了这确保这些微服务的安全性,涉及的微服务也都需要实现

spring cloud深入学习(十一)-----服务网关zuul

前面的文章我们介绍了,Eureka用于服务的注册于发现,Feign支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散,Spring Cloud Config服务集群配置中心,似乎一个微服务框架已经完成了. 我们还是少考虑了一个问题,外部的应用如何来访问内部各种各样的微服务呢?在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务.当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制

spring cloud微服务实践七

在spring cloud 2.x以后,由于zuul一直停滞在1.x版本,所以spring官方就自己开发了一个项目 Spring Cloud Gateway.作为spring cloud微服务的网关组件. 注:这一个系列的开发环境版本为 java1.8, spring boot2.x, spring cloud Greenwich.SR2, IDE为 Intelli IDEA spring cloud gateway 入门 根据官方的简介,它是spring mvc基础之上,旨在提供一个简单有效的

服务网关zuul之一:入门介绍

通过之前几篇Spring Cloud中几个核心组件的介绍,我们已经可以构建一个简略的(不够完善)微服务架构了.比如下图所示: 我们使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现:而服务间通过Ribbon或Feign实现服务的消费以及均衡负载:通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理.为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延. 在该架构中,我

Spring Cloud微服务运维神器之Consul Template?

Spring Cloud微服务架构浅析 这篇文章中要和大家分享下的就是在Spring Cloud微服务架构模式中被运维小哥用的很爽的一个工具Consul Template? 在具体介绍Consul Template是个什么东西之前,我们先来整体看一张微服务模式下的系统架构图,如下图所示: 在上图中,我们看到在基于Spring Cloud的微服务体系中,所有的微服务都会被注册到统一服务注册中心进行服务管理,这里使用的服务注册中心是Consul.假设在正常情况下,我们面向C端用户设计了一套微服务逻辑

在阿里云容器服务上开发基于Docker的Spring Cloud微服务应用

本文为阿里云容器服务Spring Cloud应用开发系列文章的第一篇. 一.在阿里云容器服务上开发Spring Cloud微服务应用(本文) 二.部署Spring Cloud应用示例 三.服务发现 四.服务间通信与集成 五.服务智能路由 六.集中配置管理 七.高可用和容错 八.监控和日志 九.服务的部署和发布策略 微服务概述 单体应用通常指在一个程序中满足多个业务或技术领域的需求,不同的需求领域内化为模块.假定我们要开发一个Web应用,通常的MVC模式可以满足要求.针对不同领域有不少代码生成工具