我爱编程

Springcloud zuul 源码阅读

2018-04-13  本文已影响145人  薛云龙

阅读过SpringCloud基于zuul的实现,大体能够了解其中主要逻辑,但是直白的来讲不知如何下笔。我们通过问题的方式,一步一步的剖析其中的细节。

看这块内容前,你要对Spring MVC中的DispatchServlet,HandlerMapping及其相关子类有所了解。简单说一下:
DispatchServlet通过handlerMapping的实现类,把所有controller加载为handler,这样每次过来请求时,根据url寻找对应controller(handler),以此响应用户。
那么下面就是一个小高潮,


ZuulHandlerMapping.java

先暂且不管Route。ZuulHandlerMapping和ZuulController是SpringCloudZuul实现了的两个类。这里的ZuulHandlerMapping将ZuulController与所有的route.getFullPath()对应起来,意思就是所有能访问到的路径,都会先走zuulController这个handler。

那么我们接着来看ZuulController的实现,


image.png
image.png

ZuulController继承了ServletWrappingController,由ServletWrappingController可知,所有的请求都会走到servletInstance的service方法。那么这个servletInstance就是com.netflix.zuul.http.ZuulServlet。看过我上篇文章NetFlix Zuul 源码阅读

1.至此,你应该能够理解为什么所有的请求都能被Filter处理?

那么,我们接着看,你还记不记得上文中的Route类了?上文中有一个操作是

private void registerHandlers() {
        Collection<Route> routes = this.routeLocator.getRoutes();
        if (routes.isEmpty()) {
            this.logger.warn("No routes found from RouteLocator");
        }
        else {
            for (Route route : routes) {
                registerHandler(route.getFullPath(), this.zuul);
            }
        }
    }

这里获取了所有的route.fullpath注册到了zuulController。这里边的Route其实就是一个路由信息的类,你看他的字段就可以得知他的作用了。
比他更关键的是SimpleRouteLocator和DiscoveryClientRouteLocator,SimpleRouteLocator能够将你配置文件中配置的所有路由信息转为Route对象的一个列表,这样你就能知道配置文件声明的所有路由表啦。那么如果你接入了注册中心,顾名思义,DiscoveryClientRouteLocator则是能够通过DiscoveryClient去注册中心获取到所有能访问的api路径。
so,通俗点来说,Route就是访问的一个接口路径+location地址的一个信息,RouteLocator更像是统一加载系统中的所有的接口路径信息的匹配器。

好,我们接下来就是另外一个小高潮。游泳健身(pre---filter)了解一下:


image.png

这里主要看PreDecorationFilter这个过滤器:

public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
        //看见了没?这里有没有根据当前请求的requestURI来找到对应的路由信息
        Route route = this.routeLocator.getMatchingRoute(requestURI);
        if (route != null) {
            String location = route.getLocation();
            if (location != null) {
                ctx.put(REQUEST_URI_KEY, route.getPath());
                ctx.put(PROXY_KEY, 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_KEY, route.getRetryable());
                }

                if (location.startsWith(HTTP_SCHEME+":") || location.startsWith(HTTPS_SCHEME+":")) {
                    ctx.setRouteHost(getUrl(location));
                    ctx.addOriginResponseHeader(SERVICE_HEADER, location);
                }
                else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {
                    ctx.set(FORWARD_TO_KEY,
                            StringUtils.cleanPath(location.substring(FORWARD_LOCATION_PREFIX.length()) + route.getPath()));
                    ctx.setRouteHost(null);
                    return null;
                }
                else {
                    // set serviceId for use in filters.route.RibbonRequest
                    ctx.set(SERVICE_ID_KEY, location);
                    ctx.setRouteHost(null);
                    ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
                }
                if (this.properties.isAddProxyHeaders()) {
                    addProxyHeaders(ctx, route);
                    String xforwardedfor = ctx.getRequest().getHeader(X_FORWARDED_FOR_HEADER);
                    String remoteAddr = ctx.getRequest().getRemoteAddr();
                    if (xforwardedfor == null) {
                        xforwardedfor = remoteAddr;
                    }
                    else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicates
                        xforwardedfor += ", " + remoteAddr;
                    }
                    ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);
                }
                if (this.properties.isAddHostHeader()) {
                    ctx.addZuulRequestHeader(HttpHeaders.HOST, toHostHeader(ctx.getRequest()));
                }
            }
        }

PreDecorationFilter主要是在请求来到时,将需要准备的信息放到线程内RequestContext。然后为下一步的route类型的filter准备数据。

image.png

看一下SimpleHostRoutingFilter的实现:

image.png

这说明这个filter生效的原因是:线程存储中有routehost这个属性并且在sendZuulResponse()为true。sendZuulResponse这个家伙主要在pre类型的filter中使用,如果sendZuulResponse未false,说明不用继续通过其他的filter直接返回结果。


image.png

这里很明显的观察到,CloseableHttpClient对request进行了转发请求,并将CloseableHttpResponse返回。

再看一下RibbonRoutingFilter

image.png

只要有注册中心服务中的serviceId,routehost==null,sendZuulResponse()为true,该filter会生效。

2. SpringCloudZuul如何实现的路径匹配和转发?

上一篇下一篇

猜你喜欢

热点阅读