微信公衆號:I am CR7
若有問題或建議,請在下方留言;
最近更新:2019-01-03css
微信公衆號:I am CR7
若有問題或建議,請在下方留言
最近更新:2019-01-03java
通過前面兩篇文章的鋪墊,大戲正式上場。本文將對zuul是如何根據配置的路由信息,轉發請求到後端微服務,進行詳細分析。補充一點:本着由淺入深的原則,這裏只對簡單URL方式進行分析,serviceId方式後續會單獨講解。python
上一篇Spring Cloud Netflix Zuul源碼分析之路由註冊篇簡化版doFilter()源碼裏,咱們講解了過濾器鏈的執行,下面,筆者將從servlet.service(request, response);入手,先來說一講,請求是如何進入到ZuulServlet中。web
1protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
2 // 一、根據請求獲取handlerMapping對應的HandlerExecutionChain
3 HandlerExecutionChain mappedHandler = getHandler(processedRequest);
4 // 二、根據請求對應的handler,獲取對應的handlerAdapter
5 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
6 // 三、調用handlerAdapter的handle方法進行處理
7 ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
8 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
9}
複製代碼
下面咱們逐一來看每一行方法內部的源碼。spring
1protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
2 if (this.handlerMappings != null) {
3 // 以前初始化保存到內存中的handlerMapping,遍歷查找請求對應的Handler
4 for (HandlerMapping hm : this.handlerMappings) {
5 if (logger.isTraceEnabled()) {
6 logger.trace(
7 "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
8 }
9 HandlerExecutionChain handler = hm.getHandler(request);
10 if (handler != null) {
11 return handler;
12 }
13 }
14 }
15 return null;
16}
複製代碼
日誌:apache
12018-12-28 15:35:07.087 [http-nio-8558-exec-2] DEBUG o.s.c.n.zuul.web.ZuulHandlerMapping - Matching patterns for request [/role-api/info/7] are [/role-api/**]
22018-12-28 15:35:29.916 [http-nio-8558-exec-2] DEBUG o.s.c.n.zuul.web.ZuulHandlerMapping - URI Template variables for request [/role-api/info/7] are {}
32018-12-28 15:35:34.000 [http-nio-8558-exec-2] DEBUG o.s.c.n.zuul.web.ZuulHandlerMapping - Mapping [/role-api/info/7] to HandlerExecutionChain with handler [org.springframework.cloud.netflix.zuul.web.ZuulController@6d25d8f8] and 1 interceptor
複製代碼
查詢handlerMappings後,找到了ZuulController,封裝到HandlerExecutionChain裏。後端
1protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
2 if (this.handlerAdapters != null) {
3 // 以前初始化保存到內存中的handlerAdapters,遍歷查找ZuulController對應的HandlerAdapter
4 for (HandlerAdapter ha : this.handlerAdapters) {
5 if (logger.isTraceEnabled()) {
6 logger.trace("Testing handler adapter [" + ha + "]");
7 }
8 // 查看HandlerAdapter是否支持當前請求對應的Handler
9 if (ha.supports(handler)) {
10 return ha;
11 }
12 }
13 }
14 throw new ServletException("No adapter for handler [" + handler +
15 "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
16}
17
18// SimpleControllerHandlerAdapter
19@Override
20public boolean supports(Object handler) {
21 // ZuulController恰好知足條件
22 return (handler instanceof Controller);
23}
複製代碼
查詢handlerAdapters後,找到了ZuulController支持的SimpleControllerHandlerAdapter。api
1// SimpleControllerHandlerAdapter
2@Override
3@Nullable
4public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
5 throws Exception {
6
7 return ((Controller) handler).handleRequest(request, response);
8}
9
10// ZuulController
11@Override
12public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
13 try {
14 // We don't care about the other features of the base class, just want to
15 // handle the request
16 return super.handleRequestInternal(request, response);
17 }
18 finally {
19 // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
20 RequestContext.getCurrentContext().unset();
21 }
22}
23
24// ServletWrappingController
25@Override
26protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
27 throws Exception {
28
29 Assert.state(this.servletInstance != null, "No Servlet instance");
30 // 到這裏,咱們就找到了調用ZuulServlet.service()的地方
31 this.servletInstance.service(request, response);
32 return null;
33}
複製代碼
至此,請求如何進入到ZuulServlet,咱們就分析完畢了。小結一下:微信
下面,咱們開始講解本文最核心的部分:ZuulServlet的service方法。markdown
1Override
2public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
3 try {
4 init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
5
6 // Marks this request as having passed through the "Zuul engine", as opposed to servlets
7 // explicitly bound in web.xml, for which requests will not have the same data attached
8 RequestContext context = RequestContext.getCurrentContext();
9 context.setZuulEngineRan();
10
11 try {
12 preRoute();
13 } catch (ZuulException e) {
14 error(e);
15 postRoute();
16 return;
17 }
18 try {
19 route();
20 } catch (ZuulException e) {
21 error(e);
22 postRoute();
23 return;
24 }
25 try {
26 postRoute();
27 } catch (ZuulException e) {
28 error(e);
29 return;
30 }
31
32 } catch (Throwable e) {
33 error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
34 } finally {
35 RequestContext.getCurrentContext().unset();
36 }
37}
複製代碼
閱讀完上述源碼,足以明白ZuulServlet的處理過程就是執行一系列過濾器。一共分爲四種類型:前置、路由、後置、錯誤。下面按照正常請求,對各種型過濾器進行逐一講解。爲了避免讓你們困惑,這裏先列出筆者的思路:
Zuul默認提供了五個前置過濾器:
該過濾器老是會被執行,主要用來檢測當前請求是經過Spring的DispatcherServlet處理運行的,仍是經過ZuulServlet來處理運行的。它的檢測結果會以布爾類型保存在當前請求上下文的isDispatcherServletRequest參數中,這樣後續的過濾器中,咱們就能夠經過RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法來判斷請求處理的源頭,以實現後續不一樣的處理機制。
該過濾器老是會被執行,主要爲了將原始的HttpServletRequest包裝成Servlet30RequestWrapper對象。
該過濾器的執行有條件要求:要麼是Context-Type爲application/x-www-form-urlencoded的請求,要麼是Context-Type爲multipart/form-data,且是由String的DispatcherServlet處理的請求。主要用來將請求包裝成FormBodyRequestWrapper對象。
該過濾器的執行有兩個條件:要麼配置裏指定zuul.debug.request爲true,要麼請求參數debug爲true。主要用來將當前請求上下文中的debugRouting和debugRequest參數設置爲true。這樣的好處是能夠靈活的調整配置或者請求參數,來決定是否啓用debug模式,從而在後續過濾器中利用debug打印一些日誌,便於線上分析問題。
該過濾器的執行要求請求上下文中不存在forward.do和serviceId參數,若是有一個存在的話,說明當前請求已經被處理過了(由於這二個信息就是根據當前請求的路由信息加載進來的)。主要用來對當前請求作預處理操做,如路由的匹配,將匹配到的路由信息存入請求上下文,便於後面的路由過濾器獲取。還包括爲HTTP頭添加信息,如X-Forwarded-Host、X-Forwarded-Port等等。
12018-12-29 14:35:13.785 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - Finding route for path: /role-api/info/7
22018-12-29 14:35:27.866 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - servletPath=/
32018-12-29 14:35:28.248 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - zuulServletPath=/zuul
42018-12-29 14:35:29.421 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - RequestUtils.isDispatcherServletRequest()=true
52018-12-29 14:35:30.492 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - RequestUtils.isZuulServletRequest()=false
62018-12-29 14:35:49.898 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - adjustedPath=/role-api/info/7
72018-12-29 14:36:09.935 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - Matching pattern:/user-api/**
82018-12-29 14:36:14.516 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - Matching pattern:/role-api/**
92018-12-29 14:36:23.042 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - route matched=ZuulRoute{id='role-api', path='/role-api/**', serviceId='null', url='http://localhost:9092/', stripPrefix=true, retryable=null, sensitiveHeaders=[], customSensitiveHeaders=false, }
複製代碼
Zuul默認提供了三個路由過濾器:
該過濾器的執行只有一個條件,那就是請求上下文中必須存在serviceId參數,即只對經過serviceId配置路由規則的請求生效,簡單URL請求不進行處理。
該過濾器的執行只有一個條件,那就是請求上下文中必須存在routeHost參數,即只對經過url配置路由規則的請求生效。該過濾器調用原生的httpclient包,對routeHost參數對應的物理地址發送請求,並無使用ribbon和hystrix,該類請求是沒有斷路器和線程隔離的保護機制。
該過濾器的執行只有一個條件,那就是請求上下文中必須存在forward.do參數,即用來處理路由規則中的forward本地跳轉。
12018-12-29 14:49:26.219 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.f.r.SimpleHostRoutingFilter - /info/7
22018-12-29 14:49:31.611 [http-nio-8558-exec-2] DEBUG o.s.c.n.z.f.r.SimpleHostRoutingFilter - localhost 9092 http
32018-12-29 14:49:50.557 [http-nio-8558-exec-2] DEBUG o.a.h.c.protocol.RequestAuthCache - Auth cache not set in the context
42018-12-29 14:49:50.584 [http-nio-8558-exec-2] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection request: [route: {}->http://localhost:9092][total kept alive: 0; route allocated: 0 of 1000; total allocated: 0 of 1000]
52018-12-29 14:49:50.907 [http-nio-8558-exec-2] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {}->http://localhost:9092][total kept alive: 0; route allocated: 1 of 1000; total allocated: 1 of 1000]
62018-12-29 14:49:50.941 [http-nio-8558-exec-2] DEBUG o.a.h.impl.execchain.MainClientExec - Opening connection {}->http://localhost:9092
72018-12-29 14:49:50.993 [http-nio-8558-exec-2] DEBUG o.a.h.i.c.DefaultHttpClientConnectionOperator - Connecting to localhost/127.0.0.1:9092
82018-12-29 14:49:51.054 [http-nio-8558-exec-2] DEBUG o.a.h.i.c.DefaultHttpClientConnectionOperator - Connection established 127.0.0.1:1172<->127.0.0.1:9092
92018-12-29 14:49:51.055 [http-nio-8558-exec-2] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 10000
102018-12-29 14:49:51.056 [http-nio-8558-exec-2] DEBUG o.a.h.impl.execchain.MainClientExec - Executing request GET /info/7 HTTP/1.1
112018-12-29 14:49:51.056 [http-nio-8558-exec-2] DEBUG o.a.h.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED
122018-12-29 14:49:51.059 [http-nio-8558-exec-2] DEBUG o.a.h.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED
132018-12-29 14:49:51.118 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /info/7 HTTP/1.1
142018-12-29 14:49:51.119 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> cache-control: no-cache
152018-12-29 14:49:51.119 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> postman-token: 70dbc6db-0aff-467b-a2d6-453b3627e856
162018-12-29 14:49:51.120 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> user-agent: PostmanRuntime/7.4.0
172018-12-29 14:49:51.120 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> accept: */*
182018-12-29 14:49:51.120 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> accept-encoding: gzip, deflate
192018-12-29 14:49:51.120 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> x-forwarded-host: localhost:8558
202018-12-29 14:49:51.120 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> x-forwarded-proto: http
212018-12-29 14:49:51.121 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> x-forwarded-prefix: /role-api
222018-12-29 14:49:51.121 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> x-forwarded-port: 8558
232018-12-29 14:49:51.121 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> x-forwarded-for: 0:0:0:0:0:0:0:1
242018-12-29 14:49:51.121 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: localhost:9092
252018-12-29 14:49:51.121 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: Keep-Alive
262018-12-29 14:49:51.122 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "GET /info/7 HTTP/1.1[\r][\n]"
272018-12-29 14:49:51.122 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "cache-control: no-cache[\r][\n]"
282018-12-29 14:49:51.123 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "postman-token: 70dbc6db-0aff-467b-a2d6-453b3627e856[\r][\n]"
292018-12-29 14:49:51.123 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "user-agent: PostmanRuntime/7.4.0[\r][\n]"
302018-12-29 14:49:51.123 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "accept: */*[\r][\n]"
312018-12-29 14:49:51.123 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "accept-encoding: gzip, deflate[\r][\n]"
322018-12-29 14:49:51.124 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "x-forwarded-host: localhost:8558[\r][\n]"
332018-12-29 14:49:51.125 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "x-forwarded-proto: http[\r][\n]"
342018-12-29 14:49:51.126 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "x-forwarded-prefix: /role-api[\r][\n]"
352018-12-29 14:49:51.127 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "x-forwarded-port: 8558[\r][\n]"
362018-12-29 14:49:51.127 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "x-forwarded-for: 0:0:0:0:0:0:0:1[\r][\n]"
372018-12-29 14:49:51.127 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: localhost:9092[\r][\n]"
382018-12-29 14:49:51.127 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
392018-12-29 14:49:51.128 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
402018-12-29 14:49:54.932 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
412018-12-29 14:49:54.933 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Type: text/plain;charset=UTF-8[\r][\n]"
422018-12-29 14:49:54.933 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Length: 30[\r][\n]"
432018-12-29 14:49:54.933 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 << "Date: Sat, 29 Dec 2018 06:49:54 GMT[\r][\n]"
442018-12-29 14:49:54.933 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
452018-12-29 14:49:54.934 [http-nio-8558-exec-2] DEBUG org.apache.http.wire - http-outgoing-0 << "hello I am is service Role-API"
462018-12-29 14:49:54.953 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200
472018-12-29 14:49:54.954 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: text/plain;charset=UTF-8
482018-12-29 14:49:54.954 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Length: 30
492018-12-29 14:49:54.954 [http-nio-8558-exec-2] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Sat, 29 Dec 2018 06:49:54 GMT
502018-12-29 14:49:55.091 [http-nio-8558-exec-2] DEBUG o.a.h.impl.execchain.MainClientExec - Connection can be kept alive indefinitely
51
複製代碼
Zuul默認提供了一個後置過濾器:
該過濾器執行條件要求請求上下文中必須包含請求響應相關的頭信息或者響應數據流或者響應體,只要有一個知足,就會執行。主要用來將請求上下文裏的響應信息寫入到響應內容,返回給請求客戶端。
12018-12-29 14:54:02.786 [http-nio-8558-exec-2] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://localhost:9092] can be kept alive indefinitely
22018-12-29 14:54:02.786 [http-nio-8558-exec-2] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
32018-12-29 14:54:02.787 [http-nio-8558-exec-2] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://localhost:9092][total kept alive: 1; route allocated: 1 of 1000; total allocated: 1 of 1000]
42018-12-29 14:55:33.931 [http-nio-8558-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
52018-12-29 14:56:22.849 [http-nio-8558-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Successfully completed request
複製代碼
以上就是一次簡單URL請求在ZuulServlet的處理過程,下面咱們深刻研究三個重要的類,分別是:前置過濾器PreDecorationFilter、路由過濾器SimpleHostRoutingFilter、後置過濾器SendResponseFilter。因篇幅緣由,後面內容請看Spring Cloud Netflix Zuul源碼分析之請求處理篇-下
到這裏,咱們就講完了一個簡單URL請求在Zuul中整個處理過程。寫做過程當中,筆者一直在思考,如何行文能讓你們更好的理解。雖然修改了不少次,可是仍是以爲不夠完美,只能一邊寫一邊總結一邊改進。但願你們多多留言,給出意見和建議,那筆者真是感激涕零!!!最後,感謝你們的支持,祝新年快樂,祁琛,2019年1月3日。