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