浅谈Spring Cloud zuul http请求转发原理
springcloud网关,依赖于netflix下的zuul组件
zuul的流程是,自定义了ZuulServletFilter和zuulServlet两种方式,让开发者可以去实现,并调用
先来看下ZuulServletFilter的实现片段
@Override publicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{ try{ init((HttpServletRequest)servletRequest,(HttpServletResponse)servletResponse); try{ preRouting(); }catch(ZuulExceptione){ error(e); postRouting(); return; } //Onlyforwardontotothechainifazuulresponseisnotbeingsent if(!RequestContext.getCurrentContext().sendZuulResponse()){ filterChain.doFilter(servletRequest,servletResponse); return; } try{ routing(); }catch(ZuulExceptione){ error(e); postRouting(); return; } try{ postRouting(); }catch(ZuulExceptione){ error(e); return; } }catch(Throwablee){ error(newZuulException(e,500,"UNCAUGHT_EXCEPTION_FROM_FILTER_"+e.getClass().getName())); }finally{ RequestContext.getCurrentContext().unset(); } }
从上面的代码可以看到,比较关心的是preRouting、routing,postRouting三个方法,这三个方法会调用注册为ZuulFilter的子类,首先来看下这三个方法
preRouting:是路由前会做一些内容
routing():开始路由事项
postRouting:路由结束,不管是否有错误都会经过该方法
那这三个方法是怎么和ZuulFilter联系在一起的呢?
先来分析下preRouting:
voidpostRouting()throwsZuulException{ zuulRunner.postRoute(); }
同时ZuulRunner再来调用
publicvoidpostRoute()throwsZuulException{ FilterProcessor.getInstance().postRoute(); }
最终调用FilterProcessor的runFilters
publicvoidpreRoute()throwsZuulException{ try{ runFilters("pre"); }catch(ZuulExceptione){ throwe; }catch(Throwablee){ thrownewZuulException(e,500,"UNCAUGHT_EXCEPTION_IN_PRE_FILTER_"+e.getClass().getName()); } }
看到了runFilters是通过filterType(pre,route,post)来过滤出已经注册的ZuulFilter:
publicObjectrunFilters(StringsType)throwsThrowable{ if(RequestContext.getCurrentContext().debugRouting()){ Debug.addRoutingDebug("Invoking{"+sType+"}typefilters"); } booleanbResult=false; //通过sType获取zuulFilter的列表 Listlist=FilterLoader.getInstance().getFiltersByType(sType); if(list!=null){ for(inti=0;i 再来看下ZuulFilter的定义
publicabstractclassZuulFilterimplementsIZuulFilter,Comparable{ privatefinalDynamicBooleanPropertyfilterDisabled= DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(),false); /** *toclassifyafilterbytype.StandardtypesinZuulare"pre"forpre-routingfiltering, *"route"forroutingtoanorigin,"post"forpost-routingfilters,"error"forerrorhandling. *Wealsosupporta"static"typeforstaticresponsesseeStaticResponseFilter. *AnyfilterTypemadebecreatedoraddedandrunbycallingFilterProcessor.runFilters(type) * *@returnAStringrepresentingthattype */ abstractpublicStringfilterType(); /** *filterOrder()mustalsobedefinedforafilter.FiltersmayhavethesamefilterOrderifprecedenceisnot *importantforafilter.filterOrdersdonotneedtobesequential. * *@returntheintorderofafilter */ abstractpublicintfilterOrder(); /** *BydefaultZuulFiltersarestatic;theydon'tcarrystate.ThismaybeoverriddenbyoverridingtheisStaticFilter()propertytofalse * *@returntruebydefault */ publicbooleanisStaticFilter(){ returntrue; } 只列出了一部分字段,但可以看到filterType和filterOrder两个字段,这两个分别是指定filter是什么类型,排序
这两个决定了实现的ZuulFilter会在什么阶段被执行,按什么顺序执行
当选择好已经注册的ZuulFilter后,会调用ZuulFilter的runFilter
publicZuulFilterResultrunFilter(){ ZuulFilterResultzr=newZuulFilterResult(); if(!isFilterDisabled()){ if(shouldFilter()){ Tracert=TracerFactory.instance().startMicroTracer("ZUUL::"+this.getClass().getSimpleName()); try{ Objectres=run(); zr=newZuulFilterResult(res,ExecutionStatus.SUCCESS); }catch(Throwablee){ t.setName("ZUUL::"+this.getClass().getSimpleName()+"failed"); zr=newZuulFilterResult(ExecutionStatus.FAILED); zr.setException(e); }finally{ t.stopAndLog(); } }else{ zr=newZuulFilterResult(ExecutionStatus.SKIPPED); } } returnzr; }其中run是一个ZuulFilter的一个抽象方法
publicinterfaceIZuulFilter{ /** *a"true"returnfromthismethodmeansthattherun()methodshouldbeinvoked * *@returntrueiftherun()methodshouldbeinvoked.falsewillnotinvoketherun()method */ booleanshouldFilter(); /** *ifshouldFilter()istrue,thismethodwillbeinvoked.thismethodisthecoremethodofaZuulFilter * *@returnSomearbitraryartifactmaybereturned.Currentimplementationignoresit. */ Objectrun(); }所以,实现ZuulFilter的子类要重写run方法,我们来看下其中一个阶段的实现PreDecorationFilter这个类是SpringCloud封装的在使用Zuul作为转发的代码服务器时进行封装的对象,目的是为了决定当前的要转发的请求是按ServiceId,Http请求,还是forward来作转发
@Override publicObjectrun(){ RequestContextctx=RequestContext.getCurrentContext(); finalStringrequestURI=this.urlPathHelper.getPathWithinApplication(ctx.getRequest()); Routeroute=this.routeLocator.getMatchingRoute(requestURI); if(route!=null){ Stringlocation=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(newString[0])); } else{ this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(newString[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 elseif(location.startsWith("forward:")){ ctx.set("forward.to", StringUtils.cleanPath(location.substring("forward:".length())+route.getPath())); ctx.setRouteHost(null); returnnull; } else{ //否则以serviceId进行转发 //setserviceIdforuseinfilters.route.RibbonRequest ctx.set("serviceId",location); ctx.setRouteHost(null); ctx.addOriginResponseHeader("X-Zuul-ServiceId",location); } if(this.properties.isAddProxyHeaders()){ addProxyHeaders(ctx,route); Stringxforwardedfor=ctx.getRequest().getHeader("X-Forwarded-For"); StringremoteAddr=ctx.getRequest().getRemoteAddr(); if(xforwardedfor==null){ xforwardedfor=remoteAddr; } elseif(!xforwardedfor.contains(remoteAddr)){//Preventduplicates xforwardedfor+=","+remoteAddr; } ctx.addZuulRequestHeader("X-Forwarded-For",xforwardedfor); } if(this.properties.isAddHostHeader()){ ctx.addZuulRequestHeader("Host",toHostHeader(ctx.getRequest())); } } } else{ log.warn("Noroutefoundforuri:"+requestURI); StringfallBackUri=requestURI; StringfallbackPrefix=this.dispatcherServletPath;//defaultfallback //servletis //DispatcherServlet if(RequestUtils.isZuulServletRequest()){ //removetheZuulservletPathfromtherequestUri log.debug("zuulServletPath="+this.properties.getServletPath()); fallBackUri=fallBackUri.replaceFirst(this.properties.getServletPath(),""); log.debug("ReplacedZuulservletpath:"+fallBackUri); } else{ //removetheDispatcherServletservletPathfromtherequestUri log.debug("dispatcherServletPath="+this.dispatcherServletPath); fallBackUri=fallBackUri.replaceFirst(this.dispatcherServletPath,""); log.debug("ReplacedDispatcherServletservletpath:"+fallBackUri); } if(!fallBackUri.startsWith("/")){ fallBackUri="/"+fallBackUri; } StringforwardURI=fallbackPrefix+fallBackUri; forwardURI=forwardURI.replaceAll("//","/"); ctx.set("forward.to",forwardURI); } returnnull; }这个前置处理,是为了后面决定以哪种ZuulFilter来处理当前的请求,如SimpleHostRoutingFilter,这个的filterType是post,当``PreDecorationFilter设置了requestContext中的RouteHost,如SimpleHostRoutingFilter中的判断
@Override publicbooleanshouldFilter(){ returnRequestContext.getCurrentContext().getRouteHost()!=null &&RequestContext.getCurrentContext().sendZuulResponse(); }在SimpleHostRoutingFilter中的run中,真正实现地址转发的内容,其实质是调用httpClient进行请求
@Override publicObjectrun(){ RequestContextcontext=RequestContext.getCurrentContext(); HttpServletRequestrequest=context.getRequest(); MultiValueMapheaders=this.helper .buildZuulRequestHeaders(request); MultiValueMap params=this.helper .buildZuulRequestQueryParams(request); Stringverb=getVerb(request); InputStreamrequestEntity=getRequestBody(request); if(request.getContentLength()<0){ context.setChunkedRequestBody(); } Stringuri=this.helper.buildZuulRequestURI(request); this.helper.addIgnoredHeaders(); try{ HttpResponseresponse=forward(this.httpClient,verb,uri,request,headers, params,requestEntity); setResponse(response); } catch(Exceptionex){ context.set(ERROR_STATUS_CODE,HttpServletResponse.SC_INTERNAL_SERVER_ERROR); context.set("error.exception",ex); } returnnull; } 最后如果是成功能,会调用注册为post的ZuulFilter,目前有两个SendErrorFilter和SendResponseFilter这两个了,一个是处理错误,一个是处理成功的结果
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。