浅谈Spring Cloud zuul http请求转发原理

spring cloud 网关,依赖于netflix 下的zuul 组件

zuul 的流程是,自定义 了ZuulServletFilter和zuulServlet两种方式,让开发者可以去实现,并调用

先来看下ZuulServletFilter的实现片段

 @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    try {
      init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
      try {
        preRouting();
      } catch (ZuulException e) {
        error(e);
        postRouting();
        return;
      }

      // Only forward onto to the chain if a zuul response is not being sent
      if (!RequestContext.getCurrentContext().sendZuulResponse()) {
        filterChain.doFilter(servletRequest, servletResponse);
        return;
      }

      try {
        routing();
      } catch (ZuulException e) {
        error(e);
        postRouting();
        return;
      }
      try {
        postRouting();
      } catch (ZuulException e) {
        error(e);
        return;
      }
    } catch (Throwable e) {
      error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
    } finally {
      RequestContext.getCurrentContext().unset();
    }
  }

从上面的代码可以看到,比较关心的是preRouting、routing,postRouting三个方法 ,这三个方法会调用 注册为ZuulFilter的子类,首先来看下这三个方法

preRouting: 是路由前会做一些内容

routing():开始路由事项

postRouting:路由结束,不管是否有错误都会经过该方法

那这三个方法是怎么和ZuulFilter联系在一起的呢?

先来分析下 preRouting:

 void postRouting() throws ZuulException {
    zuulRunner.postRoute();
  }

同时 ZuulRunner再来调用

  public void postRoute() throws ZuulException {
    FilterProcessor.getInstance().postRoute();
  }

最终调用 FilterProcessor runFilters

  public void preRoute() throws ZuulException {
    try {
      runFilters("pre");
    } catch (ZuulException e) {
      throw e;
    } catch (Throwable e) {
      throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
    }
  }

看到了runFilters 是通过 filterType(pre ,route ,post )来过滤出已经注册的 ZuulFilter:

 public Object runFilters(String sType) throws Throwable {
    if (RequestContext.getCurrentContext().debugRouting()) {
      Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
    }
    boolean bResult = false;
    //通过sType获取 zuulFilter的列表
    List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
    if (list != null) {
      for (int i = 0; i < list.size(); i++) {
        ZuulFilter zuulFilter = list.get(i);
        Object result = processZuulFilter(zuulFilter);
        if (result != null && result instanceof Boolean) {
          bResult |= ((Boolean) result);
        }
      }
    }
    return bResult;
  }

再来看下 ZuulFilter的定义

public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {

  private final DynamicBooleanProperty filterDisabled =
      DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false);

  /**
   * to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
   * "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
   * We also support a "static" type for static responses see StaticResponseFilter.
   * Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
   *
   * @return A String representing that type
   */
  abstract public String filterType();

  /**
   * filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not
   * important for a filter. filterOrders do not need to be sequential.
   *
   * @return the int order of a filter
   */
  abstract public int filterOrder();

  /**
   * By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false
   *
   * @return true by default
   */
  public boolean isStaticFilter() {
    return true;
  }

只列出了一部分字段,但可以看到filterType和filterOrder两个字段,这两个分别是指定filter是什么类型,排序

这两个决定了实现的ZuulFilter会在什么阶段被执行,按什么顺序执行

当选择好已经注册的ZuulFilter后,会调用ZuulFilter的runFilter

 public ZuulFilterResult runFilter() {
    ZuulFilterResult zr = new ZuulFilterResult();
    if (!isFilterDisabled()) {
      if (shouldFilter()) {
        Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
        try {
          Object res = run();
          zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
        } catch (Throwable e) {
          t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
          zr = new ZuulFilterResult(ExecutionStatus.FAILED);
          zr.setException(e);
        } finally {
          t.stopAndLog();
        }
      } else {
        zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
      }
    }
    return zr;
  }

其中run 是一个ZuulFilter的一个抽象方法

public interface IZuulFilter {
  /**
   * a "true" return from this method means that the run() method should be invoked
   *
   * @return true if the run() method should be invoked. false will not invoke the run() method
   */
  boolean shouldFilter();

  /**
   * if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter
   *
   * @return Some arbitrary artifact may be returned. Current implementation ignores it.
   */
  Object run();
}

所以,实现ZuulFilter的子类要重写 run方法,我们来看下 其中一个阶段的实现 PreDecorationFilter 这个类是Spring Cloud封装的在使用Zuul 作为转发的代码服务器时进行封装的对象,目的是为了决定当前的要转发的请求是按ServiceId,Http请求,还是forward来作转发

@Override
  public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
    Route route = this.routeLocator.getMatchingRoute(requestURI);
    if (route != null) {
      String location = route.getLocation();
      if (location != null) {
        ctx.put("requestURI", route.getPath());
        ctx.put("proxy", route.getId());
        if (!route.isCustomSensitiveHeaders()) {
          this.proxyRequestHelper
              .addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0]));
        }
        else {
          this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0]));
        }

        if (route.getRetryable() != null) {
          ctx.put("retryable", route.getRetryable());
        }
        // 如果配置的转发地址是http开头,会设置 RouteHost
        if (location.startsWith("http:") || location.startsWith("https:")) {
          ctx.setRouteHost(getUrl(location));
          ctx.addOriginResponseHeader("X-Zuul-Service", location);
        }
         // 如果配置的转发地址forward,则会设置forward.to
        else if (location.startsWith("forward:")) {
          ctx.set("forward.to",
              StringUtils.cleanPath(location.substring("forward:".length()) + route.getPath()));
          ctx.setRouteHost(null);
          return null;
        }
        else {
           // 否则以serviceId进行转发
          // set serviceId for use in filters.route.RibbonRequest
          ctx.set("serviceId", location);
          ctx.setRouteHost(null);
          ctx.addOriginResponseHeader("X-Zuul-ServiceId", location);
        }
        if (this.properties.isAddProxyHeaders()) {
          addProxyHeaders(ctx, route);
          String xforwardedfor = ctx.getRequest().getHeader("X-Forwarded-For");
          String remoteAddr = ctx.getRequest().getRemoteAddr();
          if (xforwardedfor == null) {
            xforwardedfor = remoteAddr;
          }
          else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicates
            xforwardedfor += ", " + remoteAddr;
          }
          ctx.addZuulRequestHeader("X-Forwarded-For", xforwardedfor);
        }
        if (this.properties.isAddHostHeader()) {
          ctx.addZuulRequestHeader("Host", toHostHeader(ctx.getRequest()));
        }
      }
    }
    else {
      log.warn("No route found for uri: " + requestURI);

      String fallBackUri = requestURI;
      String fallbackPrefix = this.dispatcherServletPath; // default fallback
                                // servlet is
                                // DispatcherServlet

      if (RequestUtils.isZuulServletRequest()) {
        // remove the Zuul servletPath from the requestUri
        log.debug("zuulServletPath=" + this.properties.getServletPath());
        fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), "");
        log.debug("Replaced Zuul servlet path:" + fallBackUri);
      }
      else {
        // remove the DispatcherServlet servletPath from the requestUri
        log.debug("dispatcherServletPath=" + this.dispatcherServletPath);
        fallBackUri = fallBackUri.replaceFirst(this.dispatcherServletPath, "");
        log.debug("Replaced DispatcherServlet servlet path:" + fallBackUri);
      }
      if (!fallBackUri.startsWith("/")) {
        fallBackUri = "/" + fallBackUri;
      }
      String forwardURI = fallbackPrefix + fallBackUri;
      forwardURI = forwardURI.replaceAll("//", "/");
      ctx.set("forward.to", forwardURI);
    }
    return null;
  }

这个前置处理,是为了后面决定以哪种ZuulFilter来处理当前的请求 ,如 SimpleHostRoutingFilter,这个的filterType是post ,当 ``PreDecorationFilter设置了requestContext中的 RouteHost,如 SimpleHostRoutingFilter中的判断

  @Override
  public boolean shouldFilter() {
    return RequestContext.getCurrentContext().getRouteHost() != null
        && RequestContext.getCurrentContext().sendZuulResponse();
  }

在 SimpleHostRoutingFilter中的run中,真正实现地址转发的内容,其实质是调用 httpClient进行请求

@Override
  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;
  }

最后如果是成功能,会调用 注册 为post的ZuulFilter ,目前有两个 SendErrorFilter 和 SendResponseFilter 这两个了,一个是处理错误,一个是处理成功的结果

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • spring cloud-zuul的Filter使用详解

    在前面我们使用zuul搭建了网关http://www.jb51.net/article/133235.htm 关于网关的作用,这里就不再次赘述了,我们今天的重点是zuul的Filter.通过Filter,我们可以实现安全控制,比如,只有请求参数中有用户名和密码的客户端才能访问服务端的资源.那么如何来实现Filter了? 要想实现Filter,需要以下几个步骤: 1.继承ZuulFilter类,为了验证Filter的特性,我们这里创建3个Filter 根据用户名来过滤 package com.ch

  • Spring Cloud zuul自定义统一异常处理实现方法

    Zuul在springcloud微服务体系中提供filer和router功能,是微服务不可或缺的部分.filer处理默认实现的外还可以自定义进行授权.限流.安全校验等,router完全可以替代Nginx反向代理.Zuul异常处理就是由SendErrorFilter完成. 在我们应用过程我们发现使用默认的异常filter有两个问题不是很友好: 1.无法快速识别出是否是请求路由的服务超时还是没有任何可用节点,发生错误只能查看日志通过堆栈去定位: 2.无法兼容自定义的譬如{code:500,msg:"

  • Spring Cloud入门教程之Zuul实现API网关与请求过滤

    简介 Zuul是Netflix基于JVM的路由器和服务器端负载均衡器.最常用的场景是替换Nginx反向代理后台微服务供前端UI访问. Zuul使用Ribbon来定位一个通过发现转发的实例,所有请求都以hystrix命令执行,所以故障将显示在Hystrix指标中. 注:Zuul不包括发现客户端,因此对于基于服务ID的路由,需要在类路径中提供其中一个路由 Zuul是Spring Cloud提供的api网关和过滤组件,它提供如下功能: 认证 过滤 压力测试 Canary测试 动态路由 服务迁移 负载均

  • Spring Cloud Zuul的重试配置详解

    Spring Cloud Zuul模块本身就包含了对于hystrix和ribbon的依赖,当我们使用zuul通过path和serviceId的组合来配置路由的时候,可以通过hystrix和ribbon的配置调整路由请求的各种时间超时机制. 1 ribbon配置举例 配置连接超时时间1秒,请求处理时间2秒,统一服务server尝试重连1次,切换server重连1次 ribbon: ConnectTimeout: 1000 ReadTimeout: 2000 MaxAutoRetries: 1 Ma

  • Spring Cloud学习教程之Zuul统一异常处理与回退

    前言 Zuul 是Netflix 提供的一个开源组件,致力于在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架.也有很多公司使用它来作为网关的重要组成部分,碰巧今年公司的架构组决定自研一个网关产品,集动态路由,动态权限,限流配额等功能为一体,为其他部门的项目提供统一的外网调用管理,最终形成产品(这方面阿里其实已经有成熟的网关产品了,但是不太适用于个性化的配置,也没有集成权限和限流降级). 本文主要给大家介绍了关于Spring Cloud Zuul统一异常处理与回退的相关内容,分享出来供大家

  • 详解SpringCloud Zuul过滤器返回值拦截

    Zuul作为网关服务,是其他各服务对外中转站,通过Zuul进行请求转发.这就涉及到部分数据是不能原封返回的,比如服务之间通信的凭证,用户的加密信息等等. 举个例子,用户服务提供一个登录接口,用户名密码正确后返回一个Token,此Token作为用户服务的通行证,那么用户登录成功后返回的Token就需要进行加密或者防止篡改处理.在到达用户服务其他接口前,就需要对Token进行校验,非法的Token就不需要转发到用户服务中了,直接在网关层返回信息即可. 要修改服务返回的信息,需要使用的是Zuul的过滤

  • spring cloud 使用Zuul 实现API网关服务问题

    通过前面几次的分享,我们了解了微服务架构的几个核心设施,通过这些组件我们可以搭建简单的微服务架构系统.比如通过Spring Cloud Eureka搭建高可用的服务注册中心并实现服务的注册和发现: 通过Spring Cloud Ribbon或Feign进行负载均衡:通过Spring Cloud Hystrix进行服务容错保护以避免故障蔓延.微服务搭建好了之后我们肯定会提供给外部系统一些统一的RESTFul API服务接口进行调用, 但是当外部系统调用我们的RESTful API的时候,怎么确定它

  • 浅谈SpringCloud之zuul源码解析

    zuul各版本实现存在一些微小的变化,总的实现思想未改变,以spring-cloud-netflix-core-1.3.6.RELEASE为例 一.zuul的重要的初始化类 org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration org.springframework.cloud.netf

  • SpringCloud实战之Zuul网关服务

    为什么需要网关呢? 我们知道我们要进入一个服务本身,很明显我们没有特别好的办法,直接输入IP地址+端口号,我们知道这样的做法很糟糕的,这样的做法大有问题,首先暴露了我们实体机器的IP地址,别人一看你的IP地址就知道服务部署在哪里,让别人很方便的进行攻击操作. 第二,我们这么多服务,我们是不是要挨个调用它呀,我们这里假设做了个权限认证,我们每一个客户访问的都是跑在不同机器上的不同的JVM上的服务程序,我们每一个服务都需要一个服务认证,这样做烦不烦呀,明显是很烦的. 那么我们这时候面临着这两个及其总

  • springcloud 中 zuul 修改请求参数信息的方法

    Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器. Zuul功能: 认证 压力测试 金丝雀测试 动态路由 负载削减 安全 静态响应处理 主动/主动交换管理 Zuul的规则引擎允许通过任何JVM语言来编写规则和过滤器, 支持基于Java和Groovy的构建. 配置属性 zuul.max.host.connections 已经被两个新的配置属性替代, zuul.host.maxTotalConnections (总连接数)和 zuul.host.maxPerRouteConnec

随机推荐