浅谈Spring Cloud zuul http请求转发原理
spring cloud 网关,依赖于netflix 下的zuul 组件
zuul 的流程是,自定义 了zuulservletfilter和zuulservlet两种方式,让开发者可以去实现,并调用
先来看下zuulservletfilter的实现片段
@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:
void postrouting() throws zuulexception { zuulrunner.postroute(); }
同时 zuulrunner
再来调用
public void postroute() throws zuulexception { filterprocessor.getinstance().postroute(); }
最终调用 filterprocessor
的 runfilters
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:
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的定义
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
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的一个抽象方法
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来作转发
@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中的判断
@override public boolean shouldfilter() { return requestcontext.getcurrentcontext().getroutehost() != null && requestcontext.getcurrentcontext().sendzuulresponse(); }
在 simplehostroutingfilter中的run中,真正实现地址转发的内容,其实质是调用 httpclient进行请求
@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 这两个了,一个是处理错误,一个是处理成功的结果
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Java标识接口的使用方法