zoukankan      html  css  js  c++  java
  • Zuul 源码的分析

    Zuul 就是个网关,过滤所有数据, 和Eureka的区别就是,前者或过滤数据,一般进行权限拦截,后者进行请求的转发,只是链接。

    Zuul包含了对请求的路由和过滤两个最主要的功能:

    使用 注解@EnableZuulProxy  引入 ZuulProxyMarkerConfiguration.class

    此时导入的配置类也会注入

    @Configuration
    @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
    		RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
    		RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class })
    @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
    public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
    

      这个配置类中主要注入了已写filter和controller之类的,具体看源码

        @Bean
        @ConditionalOnMissingBean(name = "zuulServlet")
        public ServletRegistrationBean zuulServlet() {
            ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),
                    this.zuulProperties.getServletPattern());
            // The whole point of exposing this servlet is to provide a route that doesn't
            // buffer requests.
            servlet.addInitParameter("buffer-requests", "false");
            return servlet;
        }
    初始化ZuulFilterInitializer类,将所有的filter 向FilterRegistry注册。
    @Configuration
    protected static class ZuulFilterConfiguration { @Autowired private Map<String, ZuulFilter> filters; @Bean public ZuulFilterInitializer zuulFilterInitializer( CounterFactory counterFactory, TracerFactory tracerFactory) { FilterLoader filterLoader = FilterLoader.getInstance(); FilterRegistry filterRegistry = FilterRegistry.instance();
             //FilterRegistry管理了一个ConcurrentHashMap,用作存储过滤器的
    return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry); } }

    在zuul中, 整个请求的过程是这样的,首先将请求给zuulservlet处理,zuulservlet中有一个zuulRunner对象,该对象中初始化了RequestContext:作为存储整个请求的一些数据,并被所有的zuulfilter共享。zuulRunner中还有 FilterProcessor,FilterProcessor作为执行所有的zuulfilter的管理器。FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支持groovy热加载,采用了轮询的方式热加载。有了这些filter之后,zuulservelet首先执行的Pre类型的过滤器,再执行route类型的过滤器,最后执行的是post 类型的过滤器,如果在执行这些过滤器有错误的时候则会执行error类型的过滤器。执行完这些过滤器,最终将请求的结果返回给客户端。

    ZuulServlet初始化zuulRunner

        @Override
        public void init(ServletConfig config) throws ServletException {
            super.init(config);
    
            String bufferReqsStr = config.getInitParameter("buffer-requests");
            boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
    
            zuulRunner = new ZuulRunner(bufferReqs);
        }

    zuulRunner初始化RequestContext

        public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
    
            RequestContext ctx = RequestContext.getCurrentContext();
            if (bufferRequests) {
                ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
            } else {
                ctx.setRequest(servletRequest);
            }
    
            ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
        }

    FilterProcessor过滤器处理器:

    ZuulFilter主要分类有四种:

    PRE: 该类型的filters在Request routing到源web-service之前执行。用来实现Authentication、选择源服务地址等

     

    ROUTING:该类型的filters用于把Request routing到源web-service,源web-service是实现业务逻辑的服务。这里使用HttpClient请求web-service。

     

    POST:该类型的filters在ROUTING返回Response后执行。用来实现对Response结果进行修改,收集统计数据以及把Response传输会客户端。

     

    ERROR:上面三个过程中任何一个出现错误都交由ERROR类型的filters进行处理。

    {
      pre=[
        org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter@665cb192,
        org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter@a3739e1,
        org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter@69676b9c,
        org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter@666e1965,
        com.qinsilk.cloud.gateway.filter.AccessFilter@7da324d0,
        com.qinsilk.cloud.gateway.filter.OAuthFilter@2d86f17b,
        com.qinsilk.cloud.gateway.filter.DocumentFilter@70a416c,
        org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter@4a105d1b,
        com.自定义.filter.StaticResponseFilter@4047ca4d
      ],
      route=[
        org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter@6bf6b454,
        org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter@72a14347,
        org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter@69f4143
      ],
      post=[
        org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter@717616da
      ]
    } 
    过滤器order描述类型
    ServletDetectionFilter -3 检测请求是用 DispatcherServlet还是 ZuulServlet pre
    Servlet30WrapperFilter -2 在Servlet 3.0 下,包装 requests pre
    FormBodyWrapperFilter -1 解析表单数据 pre
    SendErrorFilter 0 如果中途出现错误 error
    DebugFilter 1 设置请求过程是否开启debug pre
    PreDecorationFilter 5 根据uri决定调用哪一个route过滤器 pre
    RibbonRoutingFilter 10 如果写配置的时候用ServiceId则用这个route过滤器,该过滤器可以用Ribbon 做负载均衡,用hystrix做熔断 route
    SimpleHostRoutingFilter 100 如果写配置的时候用url则用这个route过滤 route
    SendForwardFilter 500 用RequestDispatcher请求转发 route
    SendResponseFilter 1000 用RequestDispatcher请求转发 post

    自定义的filter实现ZuulFilter 或者想在哪个filter前可配置

    public abstract class PreDescorationBaseFilter extends ZuulFilter{
        
        @Autowired
        protected ZuulProperties zuulProperties;
        
        @Resource(name = "primaryRouteLocator")
        private RouteLocator routeLocator;
        
        @Resource
        private PreDecorationFilter preDecorationFilter;
        
        private ProxyRequestHelper proxyRequestHelper = new ProxyRequestHelper();
        
        protected void router(RequestContext ctx, String newUri) {
            Route route = this.routeLocator.getMatchingRoute(newUri);
            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.zuulProperties.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 ;
                    }
                    else {
                        ctx.set(SERVICE_ID_KEY, location);
                        ctx.setRouteHost(null);
                        ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
                    }
                    
                    if (this.zuulProperties.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.zuulProperties.isAddHostHeader()) {
                        ctx.addZuulRequestHeader(HttpHeaders.HOST, toHostHeader(ctx.getRequest()));
                    }
                }
            }
        }
        
        protected URL getUrl(String target) {
            try {
                return new URL(target);
            }
            catch (MalformedURLException ex) {
                throw new IllegalStateException("Target URL is malformed", ex);
            }
        }
        
        protected void addProxyHeaders(RequestContext ctx, Route route) {
            HttpServletRequest request = ctx.getRequest();
            String host = toHostHeader(request);
            String port = String.valueOf(request.getServerPort());
            String proto = request.getScheme();
            if (hasHeader(request, X_FORWARDED_HOST_HEADER)) {
                host = request.getHeader(X_FORWARDED_HOST_HEADER) + "," + host;
            }
            if (!hasHeader(request, X_FORWARDED_PORT_HEADER)) {
                if (hasHeader(request, X_FORWARDED_PROTO_HEADER)) {
                    StringBuilder builder = new StringBuilder();
                    for (String previous : StringUtils.commaDelimitedListToStringArray(request.getHeader(X_FORWARDED_PROTO_HEADER))) {
                        if (builder.length()>0) {
                            builder.append(",");
                        }
                        builder.append(HTTPS_SCHEME.equals(previous) ? HTTPS_PORT : HTTP_PORT);
                    }
                    builder.append(",").append(port);
                    port = builder.toString();
                }
            } else {
                port = request.getHeader(X_FORWARDED_PORT_HEADER) + "," + port;
            }
            if (hasHeader(request, X_FORWARDED_PROTO_HEADER)) {
                proto = request.getHeader(X_FORWARDED_PROTO_HEADER) + "," + proto;
            }
            ctx.addZuulRequestHeader(X_FORWARDED_HOST_HEADER, host);
            ctx.addZuulRequestHeader(X_FORWARDED_PORT_HEADER, port);
            ctx.addZuulRequestHeader(X_FORWARDED_PROTO_HEADER, proto);
            addProxyPrefix(ctx, route);
        }
        
        protected void addProxyPrefix(RequestContext ctx, Route route) {
            String forwardedPrefix = ctx.getRequest().getHeader(X_FORWARDED_PREFIX_HEADER);
            String contextPath = ctx.getRequest().getContextPath();
            String prefix = StringUtils.hasLength(forwardedPrefix) ? forwardedPrefix
                    : (StringUtils.hasLength(contextPath) ? contextPath : null);
            if (StringUtils.hasText(route.getPrefix())) {
                StringBuilder newPrefixBuilder = new StringBuilder();
                if (prefix != null) {
                    if (prefix.endsWith("/") && route.getPrefix().startsWith("/")) {
                        newPrefixBuilder.append(prefix, 0, prefix.length() - 1);
                    }
                    else {
                        newPrefixBuilder.append(prefix);
                    }
                }
                newPrefixBuilder.append(route.getPrefix());
                prefix = newPrefixBuilder.toString();
            }
            if (prefix != null) {
                ctx.addZuulRequestHeader(X_FORWARDED_PREFIX_HEADER, prefix);
            }
        }
    
        protected boolean hasHeader(HttpServletRequest request, String name) {
            return StringUtils.hasLength(request.getHeader(name));
        }
    
        protected String toHostHeader(HttpServletRequest request) {
            int port = request.getServerPort();
            if ((port == HTTP_PORT && HTTP_SCHEME.equals(request.getScheme()))
                    || (port == HTTPS_PORT && HTTPS_SCHEME.equals(request.getScheme()))) {
                return request.getServerName();
            }
            else {
                return request.getServerName() + ":" + port;
            }
        }
    }
    View Code

    请求主要是在routefilter中过滤执行到 SimpleHostRoutingFilter 

    在zuul上做日志处理

    记录请求的 url,ip地址,参数,请求发生的时间,整个请求的耗时,请求的响应状态,甚至请求响应的结果等,需要写一个ZuulFliter,它应该是在请求发送给客户端之前做处理,并且在route过滤器路由之后.

    记录开始时间filter

    @Component
    public class AccessFilter extends ZuulFilter {
    
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            ctx.set("startTime",System.currentTimeMillis());
           
            return null;
        }
    }
    prefilter

    结束logfilter

    @Component
    public class LoggerFilter extends ZuulFilter {
      
    
        @Override
        public String filterType() {
            return FilterConstants.POST_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() {
            RequestContext context = RequestContext.getCurrentContext();
            HttpServletRequest request = context.getRequest();
            String method = request.getMethod();//氢气的类型,post get ..
            Map<String, String> params = HttpUtils.getParams(request);
            String paramsStr = params.toString();//请求的参数
            long statrtTime = (long) context.get("startTime");//请求的开始时间
            Throwable throwable = context.getThrowable();//请求的异常,如果有的话
            request.getRequestURI();//请求的uri
            HttpUtils.getIpAddress(request);//请求的iP地址
            context.getResponseStatusCode();//请求的状态
            long duration=System.currentTimeMillis() - statrtTime);//请求耗时
    
            return null;
        }
    
    }
    View Code
  • 相关阅读:
    ECC 构筑安全可靠的区块链
    代理模式和装饰者模式
    Context都没弄明白,还怎么做Android开发?
    如何在Android Studio中查看一个类的继承关系呢?
    Android控件的继承关系
    安卓控件体系结构
    Android View框架总结(三)View工作原理
    Laravel中用GuzzleHttp
    学习PHP好,还是Python好呢?
    ElasticSearch入门 第一篇:Windows下安装ElasticSearch
  • 原文地址:https://www.cnblogs.com/mxz1994/p/9757150.html
Copyright © 2011-2022 走看看