zoukankan      html  css  js  c++  java
  • springcloud -zuul(2-执行流程及源码)

    官方图

    1.Servlet

    zuul.servletPath默认配置为/zuul,故请求为/zuul开头的会跳过dispatcherServlet直接进入ZuulServlet,该配置可以自定义配置,例如用于大文件上传

    2.ZuulServlet中service方法

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
                try {
                    this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
                    RequestContext context = RequestContext.getCurrentContext();
                    context.setZuulEngineRan();
    
                    try {
                        //运行pre过滤器
                        this.preRoute();
                    } catch (ZuulException var12) {
                        //有异常,执行errorFilter
                        this.error(var12);
                        //再执行postFilter
                        this.postRoute();
                        return;
                    }
    
                    try {
                        //运行rote过滤器
                        this.route();
                    } catch (ZuulException var13) {
                        //有异常,执行errorFilter
                        this.error(var13);
                        //再执行postFilter
                        this.postRoute();
                        return;
                    }
    
                    try {
                        //运行post过滤器
                        this.postRoute();
                    } catch (ZuulException var11) {
                        //有异常,执行errorFilter
                        this.error(var11);
                    }
                } catch (Throwable var14) {
                    this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
                } finally {
                    RequestContext.getCurrentContext().unset();
                }
            }

    3.FilterProcessor

    其运行交由FilterProcessor中的方法runFilters,根据service中的顺序,取不同的filter类型,执行其中的run方法

    public Object runFilters(String sType) throws Throwable {
                if (RequestContext.getCurrentContext().debugRouting()) {
                    Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
                }
    
                boolean bResult = false;
                List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
                if (list != null) {
                    for(int i = 0; i < list.size(); ++i) {
                        ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
                        Object result = this.processZuulFilter(zuulFilter);//见下面zuulFilter的runFilter()
                        if (result != null && result instanceof Boolean) {
                            bResult |= ((Boolean)result).booleanValue();
                        }
                    }
                }
    
                return bResult;
            }

    zuulFilter的runFilter方法,当filter的shouldFilter()返回true时才执行run()方法

    public ZuulFilterResult runFilter() {
                ZuulFilterResult zr = new ZuulFilterResult();
                if (!this.isFilterDisabled()) {
                    if (this.shouldFilter()) {
                        Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
    
                        try {
                            Object res = this.run();
                            zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
                        } catch (Throwable var7) {
                            t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                            zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                            zr.setException(var7);
                        } finally {
                            t.stopAndLog();
                        }
                    } else {
                        zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
                    }
                }
    
                return zr;
            }

    4.获取过滤器FilterRegistry

    其中的属性private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap();
    保存所有的过滤器
    例子中有12个(其中有两个为自定义的):

    [org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter@3dc68586, 
            org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter@4001d8c1, 
            org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter@60dc1a4e, 
            org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter@7a2fce12, 
            com.example.springbootzuul.filters.AuthFilter@14fc9bd, 
            org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter@74960e9d, 
            org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter@61037caf, 
            org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter@3c88191b, 
            org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter@670342a2, 
            com.example.springbootzuul.filters.LoginPostFilter@7ed49a7f, 
            org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter@357bc488, 
            org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter@d5e575c]

    5.各个filter作用

    pre过滤器:

    org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter:
    该过滤器order值为-3,是pre阶段第一个过滤器,并且总是会运行。主要用途是判断该请求是被spring的DispatcherServlet处理还是被zuul的ZuulServlet处理,并且将判断结果设置到context中,后续处理中可以依照此结果进行个性化处理
    org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter:
    该过滤器order值为-2,是pre阶段第二个过滤器,并且总是会运行。Zuul默认仅对servlet2.5兼容,该过滤器可以将request包装成3.0兼容的形式
    org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter:
    该过滤器order值为-1,是pre阶段第三个过滤器,仅针对两类请求生效,第一种是Context-Type为application/x-www-form-urlencoded,第二种是由spring的DispatcherServlet处理的Context-Type为multipart/form-data的请求。该过滤器的主要目的是将上述两种请求包装成FormBodyRequestWrapper
    org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter:
    该过滤器order值为1,是pre阶段第四个过滤器,仅在请求参数中出现debug=true(参数名称可设置)时执行。具体执行逻辑就是在context中设置debugRouting=true及debugRequest=true。在后续执行中可以通过这两个值来预埋一些debug信息,用于出现问题时提供排查所需的信息
    org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter:
    该过滤器order值为5,是pre阶段最后一个过滤器,仅在forward.to和serviceId都没有出现在context中的时候才执行。具体来说就是对请求做一些预处理,包括使用RouteLocator获取路由信息,在context中设置一些后续处理需要的信息,还有就是在请求头中添加一些代理信息,比如X-Forwarded-For
    com.example.springbootzuul.filters.AuthFilter 自定义的。。

    route:

    org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter:
    该过滤器order值为10,是route阶段第一个过滤器,仅在context中存在serviceId的情况下运行。存在serviceId,就是说需要面向服务进行路由,服务的路由信息就是我们上面讲过的两种方式,配置文件(静态)及服务注册。具体就是创建RibbonCommandContext,然后交由ribbon和hystrix向下游服务进行请求
    org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter:
    该过滤器order值为100,是route阶段第二个过滤器,仅在context中存在routeHost的情况下运行。存在routeHost,就是说我们配置了具体的http或者https url的请求信息。具体逻辑就是通过HttpClient直接向目标url发起请求,不再经过ribbon及hystrix,所以也就没有负载均衡以及熔断
    org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter:
    该过滤器order值为500,是route阶段第三个(最后一个)过滤器,仅在context中存在forward.to的情况下运行。存在forward.to,就是说我们配置了类似forward:/index的请求信息。具体就是通过RequestDispatcher进行forward

    post:

    com.example.springbootzuul.filters.LoginPostFilter 自定义的。。
    org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter:
    该过滤器order值为1000,是post阶段最后一个过滤器,仅在context中存在zuulResponseHeaders、responseDataStream、responseBody(三者是或的关系)的情况下运行,简单来说,就是在有响应数据的时候运行。我们以responseBody举例,来看下responseBody是什么时候被设置到context中的。还记得RibbonRoutingFilter吧,在他的run方法中会调用一个setResponse方法,responseBody就是在这个方法中被设置到context中

    error:

    org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter@670342a2,
    该过滤器order值为0,是error阶段唯一一个过滤器,仅在context中存在throwable的情况下运行,也就是说有异常产生的情况下运行。将错误状态码、错误信息、异常对象设置到request中,然后forward到/error(默认,可配置)。之后我们可以自己定义一个/error端口对错误进行响应

    6.关于路由处理

    6.1 Zuul在自动配置加载时注入了2个RouteLocator

    CompositeRouteLocator:是 @Primary的,它是组合多个RouteLocator的Locator
    DiscoveryClientRouteLocator:存放至CompositeRouteLocator的属性routeLocators中,当调用RouteLocator时会调用CompositeRouteLocator中的
    DiscoveryClientRouteLocator中的locateRoutes方法运行后就已经加载了配置文件中所有路由信息,以及注册中心中的服务路由信息,有的通过URL路由,有的通过serviceId路由

    protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
                    //保存ZuulRoute的LinkedHashMap
                    LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
                    //调用父类SimpleRouteLocator#locateRoutes()
                    //加载ZuulProperties中的所有配置文件中的路由信息
                    routesMap.putAll(super.locateRoutes());
                    //如果服务发现客户端discovery存在
                    if (this.discovery != null) {
                        //将routesMap已经存在的配置文件中的ZuulRoute放入staticServices<serviceId, ZuulRoute>
                        Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>();
                        for (ZuulRoute route : routesMap.values()) {
                            String serviceId = route.getServiceId();
                            
                            //如果serviceId为null,以id作为serviceId,此情况适合 zuul.routes.xxxx=/xxxx/** 的情况
                            if (serviceId == null) {
                                serviceId = route.getId();
                            }
                            if (serviceId != null) {
                                staticServices.put(serviceId, route);
                            }
                        }
                        // Add routes for discovery services by default
                        List<String> services = this.discovery.getServices(); //到注册中心找到所有service
                        String[] ignored = this.properties.getIgnoredServices()
                                .toArray(new String[0]);
                        //遍历services
                        for (String serviceId : services) {
                            // Ignore specifically ignored services and those that were manually
                            // configured
                            String key = "/" + mapRouteToService(serviceId) + "/**";
                            //如果注册中心的serviceId在staticServices集合中,并且此路由没有配置URL
                            //那么,更新路由的location为serviceId
                            if (staticServices.containsKey(serviceId)
                                    && staticServices.get(serviceId).getUrl() == null) {
                                // Explicitly configured with no URL, cannot be ignored
                                // all static routes are already in routesMap
                                // Update location using serviceId if location is null
                                ZuulRoute staticRoute = staticServices.get(serviceId);
                                if (!StringUtils.hasText(staticRoute.getLocation())) {
                                    staticRoute.setLocation(serviceId);
                                }
                            }
                            //如果注册中心的serviceId不在忽略范围内,且routesMap中还没有包含,添加到routesMap
                            //(例子中的配置#忽略所有服务 ignoredServices: '*')
                            if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
                                    && !routesMap.containsKey(key)) {
                                // Not ignored
                                routesMap.put(key, new ZuulRoute(key, serviceId));
                            }
                        }
                    }
                    
                    // 如果routesMap中有 /** 的默认路由配置
                    if (routesMap.get(DEFAULT_ROUTE) != null) {
                        ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE);
                        // Move the defaultServiceId to the end
                        routesMap.remove(DEFAULT_ROUTE);
                        routesMap.put(DEFAULT_ROUTE, defaultRoute);
                    }
                    //将routesMap中的数据微调后,放到values<String, ZuulRoute>,返回
                    LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
                    for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
                        String path = entry.getKey();
                        // Prepend with slash if not already present.
                        if (!path.startsWith("/")) {
                            path = "/" + path;
                        }
                        if (StringUtils.hasText(this.properties.getPrefix())) {
                            path = this.properties.getPrefix() + path;
                            if (!path.startsWith("/")) {
                                path = "/" + path;
                            }
                        }
                        values.put(path, entry.getValue());
                    }
                    
                    return values;
                }

    6.2 路由前的预处理:PreDecorationFilter.run()

    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) {
                            /** //RequestContext设置 requestURI:路由的pattern路径
                            //RequestContext设置 proxy:路由id
                            //设置需要忽略的敏感头信息,要么用全局默认的,要么用路由自定义的
                            //设置重试信息
                            //如果location是 http/https开头的,RequestContext设置 routeHost:URL
                            //如果location是 forward:开头的,RequestContext设置 forward信息、routeHost:null
                            //其它 RequestContext设置 serviceId、routeHost:null、X-Zuul-ServiceId
                            //是否添加代理头信息 X-Forwarded-For
                            //是否添加Host头信息
                            */
                        }
                    }
                    // ==== 没有匹配到路由信息
                    else {
                        /**............*/
                    }
                    return null;
                }

    RouteLocator.getMatchingRoute(requestURI)

    public Route getMatchingRoute(final String path) {
                        return getSimpleMatchingRoute(path);
                    }
                    
                    protected Map<String, ZuulRoute> getRoutesMap() {
                        if (this.routes.get() == null) {
                            this.routes.set(locateRoutes());
                        }
                        return this.routes.get();
                    }
    
                    protected Route getSimpleMatchingRoute(final String path) {
                        //未初始化则初始化
                        getRoutesMap();
                        //获取准确的path:根据servlet的类型将path中的serveletPath截取掉
                        String adjustedPath = adjustPath(path);
                        //通过path匹配已初化的ZuulRoute
                        ZuulRoute route = getZuulRoute(adjustedPath);
                        //通过ZuulRoute 获取route
                        return getRoute(route, adjustedPath);
                    }

    在获取Zuulroute后通过getRoute获取到最后的Route

    protected Route getRoute(ZuulRoute route, String path) {
                        if (route == null) {
                            return null;
                        }
                        String targetPath = path;
                        //配置的前缀prefix
                        String prefix = this.properties.getPrefix();
                        if(prefix.endsWith("/")) {
                            prefix = prefix.substring(0, prefix.length() - 1);
                        }
                        //访问path以前缀开头且配置截取前缀为true(不配置默认为true),截取前缀
                        if (path.startsWith(prefix + "/") && this.properties.isStripPrefix()) {
                            targetPath = path.substring(prefix.length());
                        }
                        //配置截取前缀为true(不配置默认为true)
                        if (route.isStripPrefix()) {
                            //路由path有通配符
                            int index = route.getPath().indexOf("*") - 1;
                            if (index > 0) {
                                //最后的targetPath即为各个服务里的路径
                                String routePrefix = route.getPath().substring(0, index);
                                targetPath = targetPath.replaceFirst(routePrefix, "");
                                prefix = prefix + routePrefix;
                            }
                        }
                        //标记是否默认支持重试(未配置默认false)
                        Boolean retryable = this.properties.getRetryable();
                        if (route.getRetryable() != null) {
                            retryable = route.getRetryable();
                        }
                        return new Route(route.getId(), targetPath, route.getLocation(), prefix,
                                retryable,
                                route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null, 
                                route.isStripPrefix());
                    }

    6.3 处理路由

    RibbonRoutingFilter:使用Ribbon、Hystrix和可插入的http客户端发送请求

    public Object run() {
                        RequestContext context = RequestContext.getCurrentContext();
                        this.helper.addIgnoredHeaders();
                        try {
                            RibbonCommandContext commandContext = buildCommandContext(context);
                            ClientHttpResponse response = forward(commandContext);
                            setResponse(response);
                            return response;
                        }
                        catch (ZuulException ex) {
                            throw new ZuulRuntimeException(ex);
                        }
                        catch (Exception ex) {
                            throw new ZuulRuntimeException(ex);
                        }
                    }

    SimpleHostRoutingFilter:简单路由,通过HttpClient向预定的URL发送请求

    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 {
                            CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
                                    headers, params, requestEntity);
                            setResponse(response);
                        }
                        catch (Exception ex) {
                            throw new ZuulRuntimeException(ex);
                        }
                        return null;
                    }

    SendForwardFilter:forward到本地URL

    public Object run() {
                        try {
                            RequestContext ctx = RequestContext.getCurrentContext();
                            String path = (String) ctx.get(FORWARD_TO_KEY);
                            RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
                            if (dispatcher != null) {
                                ctx.set(SEND_FORWARD_FILTER_RAN, true);
                                if (!ctx.getResponse().isCommitted()) {
                                    dispatcher.forward(ctx.getRequest(), ctx.getResponse());
                                    ctx.getResponse().flushBuffer();
                                }
                            }
                        }
                        catch (Exception ex) {
                            ReflectionUtils.rethrowRuntimeException(ex);
                        }
                        return null;
                    }

    7.其他重要组件

    FilterRegistry:使用ConcurrentHashMap存储全部的filter
    RequestContext:请求上下文对象,继承于ConcurrentHashMap<String, Object>,用于请求时的所有参数或其他信息,该对象放入线程中的,故在各个filter中都可获取到
    //注意这里的ThreadLocal实例的initialValue()方法,当ThreadLocal的get()方法返回null的时候总是会调用initialValue()方法

    protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
                    @Override
                    protected RequestContext initialValue() {
                        try {
                            return contextClass.newInstance();
                        } catch (Throwable e) {
                            throw new RuntimeException(e);
                        }
                    }
                };
                public RequestContext() {
                    super();
                }
                public static RequestContext getCurrentContext() {
                    /**省略*/
                    if (testContext != null) return testContext;
                    //当ThreadLocal的get()方法返回null的时候总是会调用initialValue()方法,所以这里是"无则新建RequestContext"的逻辑
                    RequestContext context = threadLocal.get();
                    return context;
                }

     参考资料:

      https://www.cnblogs.com/liangzs/p/8946740.html

      https://www.jianshu.com/p/2cc9e2ba2256

      

  • 相关阅读:
    pytest--重复执行用例 pytest-repeat
    python中查询mongo数据库
    pytest--将参数打到在报告中,ids参数
    pytest-html报告中,添加描述
    pytest-html报告
    pytest -fixture的3种用法(autouse=True)
    httprunner 创建run.py文件,执行套件或case,并生成测试报告
    pytest-使用自定义标记mark
    pytest 函数传参和fixture传参数request
    loadrunner-脚本设计
  • 原文地址:https://www.cnblogs.com/lantuanqing/p/11139627.html
Copyright © 2011-2022 走看看