浅谈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);
MultiValueMapparams=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这两个了,一个是处理错误,一个是处理成功的结果
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。