SpringCloud API网关-Zuul

本文涉及的产品
云原生 API 网关,700元额度,多规格可选
简介: 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

 

相关文章
|
4月前
|
监控 负载均衡 Java
深入理解Spring Cloud中的服务网关
深入理解Spring Cloud中的服务网关
|
2月前
|
监控 负载均衡 安全
微服务(五)-服务网关zuul(一)
微服务(五)-服务网关zuul(一)
|
26天前
|
XML Java 数据格式
如何使用 Spring Cloud 实现网关
如何使用 Spring Cloud 实现网关
28 3
|
2月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
75 5
|
2月前
|
测试技术 微服务
微服务(八)-服务网关zuul(四)
微服务(八)-服务网关zuul(四)
|
2月前
|
监控 前端开发 Java
微服务(七)-服务网关zuul(三)
微服务(七)-服务网关zuul(三)
|
2月前
|
负载均衡 前端开发 安全
微服务(六)-服务网关zuul(二)
微服务(六)-服务网关zuul(二)
|
4月前
|
JSON 前端开发 Java
SpringCloud怎么搭建GateWay网关&统一登录模块
本文来分享一下,最近我在自己的项目中实现的认证服务,目前比较简单,就是可以提供一个公共的服务,专门来处理登录请求,然后我还在API网关处实现了登录拦截的效果,因为在一个博客系统中,有一些地址是可以不登录的,比方说首页;也有一些是必须登录的,比如发布文章、评论等。所以,在网关处可以支持自定义一些不需要登录的地址,一些需要登录的地址,也可以在网关处进行校验,如果未登录,可以返回JSON格式的出参,前端可以进行相关处理,比如跳转到登录页面等。
109 4
|
4月前
|
监控 负载均衡 Java
深入理解Spring Cloud中的服务网关
深入理解Spring Cloud中的服务网关
|
5月前
|
Java API 开发者
Spring Cloud Gateway中的GlobalFilter:构建强大的API网关过滤器
Spring Cloud Gateway中的GlobalFilter:构建强大的API网关过滤器
333 0