好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

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

先来看下zuulservletfilter的实现片段

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

@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:

?

1

2

3

void postrouting() throws zuulexception {

    zuulrunner.postroute();

  }

同时 zuulrunner 再来调用

?

1

2

3

public void postroute() throws zuulexception {

   filterprocessor.getinstance().postroute();

}

最终调用 filterprocessor 的 runfilters

?

1

2

3

4

5

6

7

8

9

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:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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的定义

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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的一个抽象方法

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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来作转发

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

@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中的判断

?

1

2

3

4

5

@override

public boolean shouldfilter() {

   return requestcontext.getcurrentcontext().getroutehost() != null

       && requestcontext.getcurrentcontext().sendzuulresponse();

}

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

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

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

原文链接:https://www.jianshu.com/p/295e51bc1518

查看更多关于浅谈Spring Cloud zuul http请求转发原理的详细内容...

  阅读:48次