微信公众号:如有问题或建议,请在下方留言;
最近更新:2019-01-03
微信公众号:I am CR7
如有问题或建议,请在下方留言
最近更新:2019-01-03
经过前面两篇文章的铺垫,大戏正式上场。本文将对zuul是如何根据配置的路由信息,转发请求到后端微服务,进行详细分析。补充一点:本着由浅入深的原则,这里只对简单URL方式进行分析,serviceId方式后续会单独讲解。
上一篇 Spring Cloud Netflix Zuul源码分析之路由注册篇 简化版doFilter()源码里,我们讲解了过滤器链的执行,下面,笔者将从servlet.service(request, response);入手,先来讲一讲,请求是如何进入到ZuulServlet中。
1protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 2 // 1、根据请求获取handlerMapping对应的HandlerExecutionChain 3 HandlerExecutionChain mappedHandler = getHandler(processedRequest); 4 // 2、根据请求对应的handler,获取对应的handlerAdapter 5 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 6 // 3、调用handlerAdapter的handle方法进行处理 7 ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 8 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 9} 复制代码
下面我们逐一来看每一行方法内部的源码。
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} 复制代码
日志:
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。
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方法。
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日。