欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-02-22 17:28:22
spring cloud 网关,依赖于netflix 下的zuul 组件 zuul 的流程是,自定义 了zuulservletfilter和zuulservlet两种方式...

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 这两个了,一个是处理错误,一个是处理成功的结果

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

上一篇: Java标识接口的使用方法

下一篇: