SpringCloud API网关-Zuul

简介: zuul是一个能够实现动态路由、监控、弹性扩展并且安全的API网关组件。


如果所有的微服务系统都对外提供服务,那么这些微服务系统都需要实现诸如数据验证、安全校验、接口权限等功能,这对于系统的维护非常不利,这也就是api网关存在的原因。在SpringCloud的大家庭中,使用的是zuul组件来搭建api网关。zuul是一个能够实现动态路由、监控、弹性扩展并且安全的API网关组件。

在之前的一篇博客《API网关系统架构》中,有讨论过一个成熟的网关系统应该具备注入:统一接入、安全防护、流量管控、协议转换安全、高可用、高并发、方便扩展、方便运维等能力,所以这里不过多讨论api网关应该如何设计,本文重点从源码的角度分析Zuul的实现。

 请求路由

路由的功能其实比较简单,代码层面只需要能够实现将用户的请求路径和服务提供者相对应即可,所以这一部分没有附带源码,仅简单的列举了其主要的路由规则。

1  传统路由方式

配置规则

zuul.routes.<路由名称>.path=<路径映射

zuul.routes.<路由名称>.url=<服务提供者地址>

示例如下:

zuul.routes.api-a-url.path=/test/**

zuul.routes.api-a-url.url=http://localhost:8080/test

此规则表示所有/test/**请求都会使用 http://localhost:8080/test对应的微服务上。

2  面向服务的路由方式

配置规则:

zuul.routes.<路由名称>.path=<路径映射>

zuul.routes.<路由名称>.serviceId=<服务名>

示例如下:

zuul.routes.api-a.path=/testa/**

zuul.routes.api-a.serviceId=testa-service

zuul.routes.api-b.path=/testb/**


zuul.routes.api-b.serviceid=testb- service

此规则表示所有/testa/**请求都会使用testa-service的微服务,所有/testb/**请求都会使用testb-service的微服务,当然这需要配合Eureka来使用。

 

 过滤器

如果想让底层的业务系统收到的请求都是经过安全验证的,返回结果具有统一格式等,在zuul中可以通过过滤器实现,

1  请求生命周期

1)   zuul 1.0结构

https://github.com/Netflix/zuul/wiki/How-it-Works,如下所示:

932c0a5bd8985ce2a27d845d8241119a7e6bdfd6

2)   zuul 2.0结构

https://github.com/Netflix/zuul/wiki/How-It-Works-2.0如下所示:

6c4264e102bd2b0c23144d5d8bef7d223b9f47d8

Inbound Filters是前置过滤器,Outbound Fileter是后置过滤器

 

2  过滤器

可以继承ZuulFilter创建过滤器,示例如下:

public class TestFilter extends ZuulFilter  {
    @Override
    public String filterType() {
        //pre
posterror

        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        // 
需要使用此过滤器拦截,则需要返回true

        return true;
    }

    @Override
    public Object run() {
        // 
过滤的逻辑

    }

}

filterType: 过滤器的类型,取值包括:

pre:可以在请求被路由之前调用。

routing:在路由请求时被调用。

post: 在routing和error过滤器之后被调用。

error: 处理请求时发生错误时被调用。

filterOrder:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时, 需要根据该方法返回的值来依次执行,数字越小越先执行。

shouldFilter:判断该过滤器是否需要被执行,可以利用该函数来指定过滤器的有效范围。

run:过滤器的具体逻辑

 

3  Zuul提供的过滤器

1)   处理过程

默认情况下,zuul提供的filter会按照以下顺序执行。

50d798ef3fed84c2b36a0e03396b653cdd1d6329

2)   Pre过滤器

a)    ServletDetectionFilter

用来检测当前请求是通过Spring的DispatcherServlet处理运行的,还是通过Zuu1Servlet来处理运行的。可以通过一下代码获取检测结果:

RequestContext ctx = RequestContext.getCurrentContext();
boolean isDetectionRequest = ctx.get(RequestUtils.IS_DISPATCHERSERVLETREQUEST);

一般情况下,发送到API网关的外部请求都会被Spring的DispatcherServlet处理,除了通过/zuul/路径访问的请求外,因为此种请求会被Zuu1Servlet处理,主要用来应对处理大文件上传的情况。可以通过zuul.servletPath参数来进行修改。

b)    Servlet30WrapperFilter

主要为了将HttpServletRequest包装成Servlet30RequestWrapper对象。

c)    FormBodyWrapperFilter

该过滤器仅对contentType为application/x-www-form-urlencoded或者multipart/form-data的请求有效。作用是将HttpServletRequest包装成FormBodyRequestWrapper对象。

d)    DebugFilter

根据配置参数zuul.debug.requet和请求中的debug参数来决定是否执行过滤器中的操作,其代码如下:

@Override
public boolean shouldFilter() {
   HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
   if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {
      return true;
   }
   return ROUTING_DEBUG.get();
}

@Override
public Object run() {
   RequestContext ctx = RequestContext.getCurrentContext();
   ctx.setDebugRouting(true);
   ctx.setDebugRequest(true);
   return null;
}

e)    PreDecorationFilter

该过滤器会判断当前请求上下文中是否存在forward.to和serviceId参数 ,如 果都不存在,那么会执行过滤器的操作。

此过滤器的内容是为当前请求做一些预处理,比如,进行路由规则的匹配、 在请求上下文中设置该请求的基本信息以及将路由匹配结果等一些设置信息等,这些信息将是后续过滤器进行处理的重要依据,我们可以通过RequestContext.getCurrentContext ()来 访问这些信息。 

 

3)   Routing过滤器

a)    RibbonRoutingFilter

该过滤器只对请求上下文中存在serviceid参数的请求进行处理,即只对通 过service工d配置路由规则的请求生效。而该过滤器的执行逻辑就是面向服务路 由的核心,它通过使用Ribbon和Hystrix来向服务实例发起请求,并将服务实例的请求结果返回。

b)    SimpleHostRoutingFilter

该过滤器只对通过url配置路由规则的请求生效。而该过滤器的执行逻辑就是直接向routeHost参数的物理地址发起请求。

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 (request.getContentLength() < 0) {
      context.setChunkedRequestBody();
   }
   String uri = this.helper.buildZuulRequestURI(request);
   this.helper.addIgnoredHeaders();
   try {
      HttpResponse response = forward(this.httpClient, verb, uri, request, headers,
            params, requestEntity);
      setResponse(response);
   }
   catch (Exception ex) {
      context.set(ERROR_STATUS_CODE,
           HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
     context.set("error.exception", ex);
   }
   return null;
}

 

c)    SendForwardFilter

仅仅对forward请求有效,该过滤器只对请求上下文中存在forward.to参数的请求进行处理。

@Override
public boolean shouldFilter() {
   RequestContext ctx = RequestContext.getCurrentContext();
   return ctx.containsKey("forward.to")
         && !ctx.getBoolean(SEND_FORWARD_FILTER_RAN, false);
}

@Override
public Object run() {
   try {
      RequestContext ctx = RequestContext.getCurrentContext();
      String path = (String) ctx.get("forward.to");
      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());
           ctx.getResponse().flushBuffer();
         }
      }
   }
   catch (Exception ex) {
     ReflectionUtils.rethrowRuntimeException(ex);
   }
   return null;
}

 

4)   post过滤器 

a)    SendErrorFilter

该过滤器仅在请求上下文中 包含error.status_code参数并且还没有被该过滤器处理过的时候执行。该过滤器逻辑就是forward到API网关/error来影响错误信息。

@Override
public boolean shouldFilter() {
   RequestContext ctx = RequestContext.getCurrentContext();
   // only forward to errorPath if it hasn't been forwarded to already
   return ctx.containsKey("error.status_code")
         && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
}

@Override
public Object run() {
   try {
      RequestContext ctx = RequestContext.getCurrentContext();
      HttpServletRequest request = ctx.getRequest();

      int statusCode = (Integer) ctx.get("error.status_code");
     request.setAttribute("javax.servlet.error.status_code", statusCode);

      if (ctx.containsKey("error.exception")) {
         Object e = ctx.get("error.exception");
         log.warn("Error during filtering", Throwable.class.cast(e));
        request.setAttribute("javax.servlet.error.exception", e);
      }

      if (ctx.containsKey("error.message")) {
         String message = (String) ctx.get("error.message");
        request.setAttribute("javax.servlet.error.message", message);
      }

      RequestDispatcher dispatcher = request.getRequestDispatcher(
            this.errorPath);
      if (dispatcher != null) {
         ctx.set(SEND_ERROR_FILTER_RAN, true);
         if (!ctx.getResponse().isCommitted()) {
            dispatcher.forward(request, ctx.getResponse());
         }
      }
   }
   catch (Exception ex) {
     ReflectionUtils.rethrowRuntimeException(ex);
   }
   return null;
}

 

b)    SendResponseFilter

该过滤器会检查请求上下文中是否包含请求响应相关的头信息、响应数据流或是响应体,只要包含其中一个的时候执行处理逻辑。

该过滤器的处理逻辑是利用请求上下文的响应信息来组织需要发送回客户端的响应内容。 

@Override
public boolean shouldFilter() {
return !RequestContext.getCurrentContext().getZuulResponseHeaders().isEmpty()
         || RequestContext.getCurrentContext().getResponseDataStream() != null
         || RequestContext.getCurrentContext().getResponseBody() != null;
}

@Override
public Object run() {
   try {
      addResponseHeaders();
      writeResponse();
   }
   catch (Exception ex) {
     ReflectionUtils.rethrowRuntimeException(ex);
   }
   return null;
}

 

4   禁用过滤器 

默认情况下,过滤器都是启用状态的。那么有哪些方式来禁用过滤器呢?

1)   shouldFilter返回false

@Override
public boolean shouldFilter() {
        return false;
}

这种方式对于自定义过滤器来说,可以实现禁用的功能,但是不太方便,因为每次修改都要重新修改代码,重新打包。

2)   配置项

Zuul中特别提供了配置项来禁用过滤器,该配置格式如下: 

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

<SimpleClassName>代表过滤器的类名,< filterType >代表过滤器类型。如果自定义了一个前置过滤器类AccessFilter,当希望禁用此过滤器类时,可以这样配置:

zuul.AccessFilter.pre.disable=true

 

 

 参考文档

1  博客

zuul wiki

How It Works 2.0

 

2  书籍

《Spring Cloud微服务实战》

代码:https://github.com/dyc87112/SpringCloudBook.git

 

相关文章
|
9月前
|
设计模式 缓存 开发框架
「第二部:容器和微服务架构](10) API网关模式与客户端直接通信2
「第二部:容器和微服务架构](10) API网关模式与客户端直接通信2
|
9月前
|
消息中间件 开发框架 负载均衡
「第二部:容器和微服务架构](9) API网关模式与客户端直接通信
「第二部:容器和微服务架构](9) API网关模式与客户端直接通信
|
9月前
|
设计模式 负载均衡 监控
「微服务架构」面向CTO的微服务设计模式:API网关、前端的后端等
「微服务架构」面向CTO的微服务设计模式:API网关、前端的后端等
|
9月前
|
消息中间件 负载均衡 安全
[微服务架构系列]API网关.微服务简介,第2部分
[微服务架构系列]API网关.微服务简介,第2部分
|
9月前
|
JSON 负载均衡 网络协议
「微服务架构」部署NGINX Plus作为API网关,第1部分
「微服务架构」部署NGINX Plus作为API网关,第1部分
BXA
|
9月前
|
负载均衡 安全 前端开发
Spring Boot和Spring Cloud实现微服务架构下的API网关
为了满足微服务架构下的挑战API网关应运而生。API网关是服务的单一入口,提供了路由、转发、安全性、监测和协议转换等功能。API网关能够管理和保护API接口、统一访问和处理各个微服务的请求,同时能够提高服务的可用性和可靠性。
BXA
192 0
|
9月前
|
消息中间件 负载均衡 安全
[微服务架构]API网关.微服务简介,第2部分
[微服务架构]API网关.微服务简介,第2部分
|
运维 监控 安全
长连接网关技术专题(八):B站基于微服务的API网关从0到1的演进之路
也就是在这一年,B 站开始正式用 Go 重构 B 站,从此B站的API网关技术子开始了从0到1的持续演进。。。
597 0
长连接网关技术专题(八):B站基于微服务的API网关从0到1的演进之路
|
API 微服务
微服务项目服务管理混乱?来看这一篇生产者消费者服务实践,使用API网关实现服务聚合
本文针对微服务项目中的多个服务难以管理的问题,提出了一个使用API网关对微服务中的多种服务进行管理的方案,使用API网关实现微服务中的服务聚合管理。介绍了微服务中的API Gateway网关技术的使用方式和在项目中集成使用API Gateway的具体配置。通过详细的实例说明了API网关技术的具体使用以及聚合的服务和版本的配置。
259 0
微服务项目服务管理混乱?来看这一篇生产者消费者服务实践,使用API网关实现服务聚合
|
缓存 负载均衡 监控
微服务技术栈:API网关中心,落地实现方案
微服务网关从感觉上,很像是:拦截器+路由+过滤器,拦截请求,系列基础处理,路由转发到指定服务。
1683 0
微服务技术栈:API网关中心,落地实现方案

相关产品

  • 云迁移中心