• Zuul简单使用以及原理


      简单研究下Zuul简单使用以及原理.

    1. 使用

    0. pom如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud</artifactId>
            <groupId>cn.qz.cloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-gateway-zuul9526</artifactId>
    
        <dependencies>
            <!--zuul-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
            <!--eureka-client-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!--引入自己抽取的工具包-->
            <dependency>
                <groupId>cn.qz.cloud</groupId>
                <artifactId>cloud-api-commons</artifactId>
                <version>${project.version}</version>
            </dependency>
            <!--一般基础配置类-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    </project>
    View Code

      最终zuul-core 是1.3.1 版本。

    1. 新增filter

    PreFilter 前置处理器

    package cn.qz.cloud.filter;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Enumeration;
    
    @Component
    public class PreFilter extends ZuulFilter {
    
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return -1;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        /**
         * 返回值好像没什么实际意义, @see com.netflix.zuul.FilterProcessor#runFilters(java.lang.String)
         *
         * @return 可以返回任意的对象,当前实现忽略。
         * @throws ZuulException
         */
        @Override
        public Object run() throws ZuulException {
            // RequestContext 继承自ConcurrentHashMap, 用于维护当前请求的一些上下文参数, 内部用threadLocal 维护当前请求的当前RequestContext。
            HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String key = headerNames.nextElement();
                String header = request.getHeader(key);
                System.out.println("key: " + key + "\tvalue: " + header);
            }
            return null;
        }
    }
    View Code

    PostFilter 后置处理器

    package cn.qz.cloud.filter;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
    import org.springframework.stereotype.Component;
    
    @Component
    public class PostFilter extends ZuulFilter {
        @Override
        public String filterType() {
            return FilterConstants.POST_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return -1;
        }
    
        @Override
        public boolean shouldFilter() {
            RequestContext ctx = RequestContext.getCurrentContext();
            return ctx.sendZuulResponse();
        }
    
        @Override
        public Object run() throws ZuulException {
            RequestContext ctx = RequestContext.getCurrentContext();
            System.out.println("========cn.qz.cloud.filter.PostFilter.run");
            System.out.println(ctx);
            return null;
        }
    }
    View Code

    2. 主启动类

    package cn.qz.cloud;
    
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    
    @EnableZuulProxy
    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    View Code

    3.  配置文件 application.yml

    server:
      port: 9526
    
    spring:
      application:
        name: cloud-zuul
      servlet:
        multipart:
          max-file-size: 2048MB
          max-request-size: 2048MB
    
    
    eureka:
      instance:
        hostname: cloud-gateway-service
      client: #服务提供者provider注册进eureka服务列表内
        service-url:
          register-with-eureka: true
          fetch-registry: true
          defaultZone: http://localhost:7001/eureka
    
    zuul:
      routes:
        # 第一种配置方式,这种方式会创建两个 ZuulRoute 对象。 一个是: /api/v1/payment/** 路由转发到服务 cloud-payment-service; 第二个是 /cloud-payment-service/** 转发到服务 cloud-payment-service
        cloud-payment-service:
          path: /api/v1/payment/**
          sensitive-headers: Cookie,Set-Cookie
          service-id: CLOUD-PAYMENT-SERVICE
        # 下面这种方式等价于 /orders/** 开头的所有请求都交给orders 服务处理
        orders: /orders/**
        # URL 跳转的方式,不走ribbon 路由服务。访问: http://localhost:9526/guonei
        guonei:
          path: /guonei/**
          url: http://news.baidu.com/guonei
    
      retryable: false
      ribbon:
        eager-load:
          enabled: false
      servlet-path: /cloud-zuul
    
    ribbon:
      ReadTimeout: 600000
      SocketTimeout: 600000
      ConnectTimeout: 80000
    View Code

    4. 接下来就可以通过zuul 实现请求转发,测试如下:

    xxx /e/ideaspace/test (dev)
    $ curl http://localhost:9526/api/v1/payment/pay/getServerPort
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    52    0    52    0     0   1040      0 --:--:-- --:--:-- --:--:--  1061{"success":true,"code":"200","msg":"","data":"8081"}
    
    xxx /e/ideaspace/test (dev)
    $ curl http://localhost:9526/cloud-zuul/api/v1/payment/pay/getServerPort
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    52    0    52    0     0    928      0 --:--:-- --:--:-- --:--:--   962{"success":true,"code":"200","msg":"","data":"8081"}
    
    xxx /e/ideaspace/test (dev)
    $ curl http://localhost:9526/cloud-payment-service/pay/getServerPort
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    52    0    52    0     0    269      0 --:--:-- --:--:-- --:--:--   270{"success":true,"code":"200","msg":"","data":"8081"}
    
    
    xxx /e/ideaspace/test (dev)
    $ curl http://localhost:9526/cloud-zuul/cloud-payment-service/pay/getServerPort
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    52    0    52    0     0   1209      0 --:--:-- --:--:-- --:--:--  1268{"success":true,"code":"200","msg":"","data":"8081"}

      浏览器访问: http://localhost:9526/guonei 可以看到调整到百度新闻首页。

      /api/v1/payment/pay/getServerPort 和 /cloud-payment-service/pay/getServerPort 应该走的是一套机制; /cloud-zuul/api/v1/payment/pay/getServerPort 和/cloud-zuul/cloud-payment-service/pay/getServerPort 是一套机制。下面研究两套流程逻辑。

    2. 原理

    1. 前置

      基于SpringMVC的DispatcherServlet + zuul 的自己的Servlet + Filter 机制来实现。

      有两套机制: 第一套走SpringMVC(基于DispatcherServlet自定义HandlerMapping + Handler实现逻辑), 第二套不走SpingMVC机制(相当于直接向容器注册Servlet)。

    2. 整合原理

      org.springframework.cloud.netflix.zuul.EnableZuulProxy 注解开启自动整合zuul。引org.springframework.cloud.netflix.zuul.ZuulProxyMarkerConfiguration配置类,实际上是注入一个Marker类,这也是Spring starter经常采用的一种方式。

    @EnableCircuitBreaker
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(ZuulProxyMarkerConfiguration.class)
    public @interface EnableZuulProxy {
    
    }
    
    ------------
    
    @Configuration(proxyBeanMethods = false)
    public class ZuulProxyMarkerConfiguration {
    
        @Bean
        public Marker zuulProxyMarkerBean() {
            return new Marker();
        }
    
        class Marker {
    
        }
    
    }

    引入自动配置类:org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration

    @Configuration(proxyBeanMethods = false)
    @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
            RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
            RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
            HttpClientConfiguration.class })
    @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
    public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
    
        @SuppressWarnings("rawtypes")
        @Autowired(required = false)
        private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList();
    
        @Autowired(required = false)
        private Registration registration;
    
        @Autowired
        private DiscoveryClient discovery;
    
        @Autowired
        private ServiceRouteMapper serviceRouteMapper;
    
        @Override
        public HasFeatures zuulFeature() {
            return HasFeatures.namedFeature("Zuul (Discovery)",
                    ZuulProxyAutoConfiguration.class);
        }
    
        @Bean
        @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
        public DiscoveryClientRouteLocator discoveryRouteLocator() {
            return new DiscoveryClientRouteLocator(this.server.getServlet().getContextPath(),
                    this.discovery, this.zuulProperties, this.serviceRouteMapper,
                    this.registration);
        }
    
        // pre filters
        @Bean
        @ConditionalOnMissingBean(PreDecorationFilter.class)
        public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
                ProxyRequestHelper proxyRequestHelper) {
            return new PreDecorationFilter(routeLocator,
                    this.server.getServlet().getContextPath(), this.zuulProperties,
                    proxyRequestHelper);
        }
    
        // route filters
        @Bean
        @ConditionalOnMissingBean(RibbonRoutingFilter.class)
        public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
                RibbonCommandFactory<?> ribbonCommandFactory) {
            RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
                    this.requestCustomizers);
            return filter;
        }
    
        @Bean
        @ConditionalOnMissingBean({ SimpleHostRoutingFilter.class,
                CloseableHttpClient.class })
        public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
                ZuulProperties zuulProperties,
                ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
                ApacheHttpClientFactory httpClientFactory) {
            return new SimpleHostRoutingFilter(helper, zuulProperties,
                    connectionManagerFactory, httpClientFactory);
        }
    
        @Bean
        @ConditionalOnMissingBean({ SimpleHostRoutingFilter.class })
        public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
                ZuulProperties zuulProperties, CloseableHttpClient httpClient) {
            return new SimpleHostRoutingFilter(helper, zuulProperties, httpClient);
        }
    
        @Bean
        @ConditionalOnMissingBean(ServiceRouteMapper.class)
        public ServiceRouteMapper serviceRouteMapper() {
            return new SimpleServiceRouteMapper();
        }
    
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnMissingClass("org.springframework.boot.actuate.health.Health")
        protected static class NoActuatorConfiguration {
    
            @Bean
            public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
                ProxyRequestHelper helper = new ProxyRequestHelper(zuulProperties);
                return helper;
            }
    
        }
    
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(Health.class)
        protected static class EndpointConfiguration {
    
            @Autowired(required = false)
            private HttpTraceRepository traces;
    
            @Bean
            @ConditionalOnEnabledEndpoint
            public RoutesEndpoint routesEndpoint(RouteLocator routeLocator) {
                return new RoutesEndpoint(routeLocator);
            }
    
            @ConditionalOnEnabledEndpoint
            @Bean
            public FiltersEndpoint filtersEndpoint() {
                FilterRegistry filterRegistry = FilterRegistry.instance();
                return new FiltersEndpoint(filterRegistry);
            }
    
            @Bean
            public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
                TraceProxyRequestHelper helper = new TraceProxyRequestHelper(zuulProperties);
                if (this.traces != null) {
                    helper.setTraces(this.traces);
                }
                return helper;
            }
    
        }
    
    }
    View Code

    父类org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration:

    @Configuration(proxyBeanMethods = false)
    @EnableConfigurationProperties({ ZuulProperties.class })
    @ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
    @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
    // Make sure to get the ServerProperties from the same place as a normal web app would
    // FIXME @Import(ServerPropertiesAutoConfiguration.class)
    public class ZuulServerAutoConfiguration {
    
        @Autowired
        protected ZuulProperties zuulProperties;
    
        @Autowired
        protected ServerProperties server;
    
        @Autowired(required = false)
        private ErrorController errorController;
    
        private Map<String, CorsConfiguration> corsConfigurations;
    
        @Autowired(required = false)
        private List<WebMvcConfigurer> configurers = emptyList();
    
        @Bean
        public HasFeatures zuulFeature() {
            return HasFeatures.namedFeature("Zuul (Simple)",
                    ZuulServerAutoConfiguration.class);
        }
    
        @Bean
        @Primary
        public CompositeRouteLocator primaryRouteLocator(
                Collection<RouteLocator> routeLocators) {
            return new CompositeRouteLocator(routeLocators);
        }
    
        @Bean
        @ConditionalOnMissingBean(SimpleRouteLocator.class)
        public SimpleRouteLocator simpleRouteLocator() {
            return new SimpleRouteLocator(this.server.getServlet().getContextPath(),
                    this.zuulProperties);
        }
    
        @Bean
        public ZuulController zuulController() {
            return new ZuulController();
        }
    
        @Bean
        public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes,
                ZuulController zuulController) {
            ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController);
            mapping.setErrorController(this.errorController);
            mapping.setCorsConfigurations(getCorsConfigurations());
            return mapping;
        }
    
        protected final Map<String, CorsConfiguration> getCorsConfigurations() {
            if (this.corsConfigurations == null) {
                ZuulCorsRegistry registry = new ZuulCorsRegistry();
                this.configurers.forEach(configurer -> configurer.addCorsMappings(registry));
                this.corsConfigurations = registry.getCorsConfigurations();
            }
            return this.corsConfigurations;
        }
    
        @Bean
        public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
            return new ZuulRefreshListener();
        }
    
        @Bean
        @ConditionalOnMissingBean(name = "zuulServlet")
        @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false",
                matchIfMissing = true)
        public ServletRegistrationBean zuulServlet() {
            ServletRegistrationBean<ZuulServlet> 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;
        }
    
        @Bean
        @ConditionalOnMissingBean(name = "zuulServletFilter")
        @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true",
                matchIfMissing = false)
        public FilterRegistrationBean zuulServletFilter() {
            final FilterRegistrationBean<ZuulServletFilter> filterRegistration = new FilterRegistrationBean<>();
            filterRegistration.setUrlPatterns(
                    Collections.singleton(this.zuulProperties.getServletPattern()));
            filterRegistration.setFilter(new ZuulServletFilter());
            filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
            // The whole point of exposing this servlet is to provide a route that doesn't
            // buffer requests.
            filterRegistration.addInitParameter("buffer-requests", "false");
            return filterRegistration;
        }
    
        // pre filters
    
        @Bean
        public ServletDetectionFilter servletDetectionFilter() {
            return new ServletDetectionFilter();
        }
    
        @Bean
        @ConditionalOnMissingBean
        public FormBodyWrapperFilter formBodyWrapperFilter() {
            return new FormBodyWrapperFilter();
        }
    
        @Bean
        @ConditionalOnMissingBean
        public DebugFilter debugFilter() {
            return new DebugFilter();
        }
    
        @Bean
        @ConditionalOnMissingBean
        public Servlet30WrapperFilter servlet30WrapperFilter() {
            return new Servlet30WrapperFilter();
        }
    
        // post filters
    
        @Bean
        public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
            return new SendResponseFilter(zuulProperties);
        }
    
        @Bean
        public SendErrorFilter sendErrorFilter() {
            return new SendErrorFilter();
        }
    
        @Bean
        public SendForwardFilter sendForwardFilter() {
            return new SendForwardFilter();
        }
    
        @Bean
        @ConditionalOnProperty("zuul.ribbon.eager-load.enabled")
        public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer(
                SpringClientFactory springClientFactory) {
            return new ZuulRouteApplicationContextInitializer(springClientFactory,
                    zuulProperties);
        }
    
        @Configuration(proxyBeanMethods = false)
        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();
                return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,
                        filterLoader, filterRegistry);
            }
    
        }
    
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(MeterRegistry.class)
        protected static class ZuulCounterFactoryConfiguration {
    
            @Bean
            @ConditionalOnBean(MeterRegistry.class)
            @ConditionalOnMissingBean(CounterFactory.class)
            public CounterFactory counterFactory(MeterRegistry meterRegistry) {
                return new DefaultCounterFactory(meterRegistry);
            }
    
        }
    
        @Configuration(proxyBeanMethods = false)
        protected static class ZuulMetricsConfiguration {
    
            @Bean
            @ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry")
            @ConditionalOnMissingBean(CounterFactory.class)
            public CounterFactory counterFactory() {
                return new EmptyCounterFactory();
            }
    
            @ConditionalOnMissingBean(TracerFactory.class)
            @Bean
            public TracerFactory tracerFactory() {
                return new EmptyTracerFactory();
            }
    
        }
    
        private static class ZuulRefreshListener
                implements ApplicationListener<ApplicationEvent> {
    
            @Autowired
            private ZuulHandlerMapping zuulHandlerMapping;
    
            private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();
    
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                if (event instanceof ContextRefreshedEvent
                        || event instanceof RefreshScopeRefreshedEvent
                        || event instanceof RoutesRefreshedEvent
                        || event instanceof InstanceRegisteredEvent) {
                    reset();
                }
                else if (event instanceof ParentHeartbeatEvent) {
                    ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
                    resetIfNeeded(e.getValue());
                }
                else if (event instanceof HeartbeatEvent) {
                    HeartbeatEvent e = (HeartbeatEvent) event;
                    resetIfNeeded(e.getValue());
                }
            }
    
            private void resetIfNeeded(Object value) {
                if (this.heartbeatMonitor.update(value)) {
                    reset();
                }
            }
    
            private void reset() {
                this.zuulHandlerMapping.setDirty(true);
            }
    
        }
    
        private static class ZuulCorsRegistry extends CorsRegistry {
    
            @Override
            protected Map<String, CorsConfiguration> getCorsConfigurations() {
                return super.getCorsConfigurations();
            }
    
        }
    
    }
    View Code

      可以看到上面配置,ZuulProxyAutoConfiguration 相比 ZuulServerAutoConfiguration 增加了一些Ribbon相关的代理,也就是支持从注册中心拿服务信息。这也是EnableZuulProxy注解和EnableZuulServer 的区别,两个分别引入下面配置类:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(ZuulServerMarkerConfiguration.class)
    public @interface EnableZuulServer {
    }
    
    --------
    @EnableCircuitBreaker
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(ZuulProxyMarkerConfiguration.class)
    public @interface EnableZuulProxy {
    }

    官方解释如下:

    ​   Spring Cloud Netflix会根据使用哪个注释来启用Zuul安装多个过滤器。`@EnableZuulProxy`是`@EnableZuulServer`的超集。换句话说,`@EnableZuulProxy`包含`@EnableZuulServer`安装的所有过滤器。“代理”中的其他过滤器启用路由功能。如果你想要一个“空白”Zuul,你应该使用`@EnableZuulServer`。

       org.springframework.cloud.netflix.zuul.filters.ZuulProperties: 属性类,解析配置文件的配置,可以看到将配置信息维护到Map<String, ZuulRoute> routes对象中, 并且org.springframework.cloud.netflix.zuul.filters.ZuulProperties#init 用@PostConstruct 在初始化之前进行路径的规范化。

    @ConfigurationProperties("zuul")
    public class ZuulProperties {
    
        /**
         * Headers that are generally expected to be added by Spring Security, and hence often
         * duplicated if the proxy and the backend are secured with Spring. By default they
         * are added to the ignored headers if Spring Security is present and
         * ignoreSecurityHeaders = true.
         */
        public static final List<String> SECURITY_HEADERS = Arrays.asList("Pragma",
                "Cache-Control", "X-Frame-Options", "X-Content-Type-Options",
                "X-XSS-Protection", "Expires");
    
        /**
         * A common prefix for all routes.
         */
        private String prefix = "";
    
        /**
         * Flag saying whether to strip the prefix from the path before forwarding.
         */
        private boolean stripPrefix = true;
    
        /**
         * Flag for whether retry is supported by default (assuming the routes themselves
         * support it).
         */
        private Boolean retryable = false;
    
        /**
         * Map of route names to properties.
         */
        private Map<String, ZuulRoute> routes = new LinkedHashMap<>();
    
        /**
         * Flag to determine whether the proxy adds X-Forwarded-* headers.
         */
        private boolean addProxyHeaders = true;
    
        /**
         * Flag to determine whether the proxy forwards the Host header.
         */
        private boolean addHostHeader = false;
    
        /**
         * Set of service names not to consider for proxying automatically. By default all
         * services in the discovery client will be proxied.
         */
        private Set<String> ignoredServices = new LinkedHashSet<>();
    
        private Set<String> ignoredPatterns = new LinkedHashSet<>();
    
        /**
         * Names of HTTP headers to ignore completely (i.e. leave them out of downstream
         * requests and drop them from downstream responses).
         */
        private Set<String> ignoredHeaders = new LinkedHashSet<>();
    
        /**
         * Flag to say that SECURITY_HEADERS are added to ignored headers if spring security
         * is on the classpath. By setting ignoreSecurityHeaders to false we can switch off
         * this default behaviour. This should be used together with disabling the default
         * spring security headers see
         * https://docs.spring.io/spring-security/site/docs/current/reference/html/headers.html#default-security-headers
         */
        private boolean ignoreSecurityHeaders = true;
    
        /**
         * Flag to force the original query string encoding when building the backend URI in
         * SimpleHostRoutingFilter. When activated, query string will be built using
         * HttpServletRequest getQueryString() method instead of UriTemplate. Note that this
         * flag is not used in RibbonRoutingFilter with services found via DiscoveryClient
         * (like Eureka).
         */
        private boolean forceOriginalQueryStringEncoding = false;
    
        /**
         * Path to install Zuul as a servlet (not part of Spring MVC). The servlet is more
         * memory efficient for requests with large bodies, e.g. file uploads.
         */
        private String servletPath = "/zuul";
    
        private boolean ignoreLocalService = true;
    
        /**
         * Host properties controlling default connection pool properties.
         */
        private Host host = new Host();
    
        /**
         * Flag to say that request bodies can be traced.
         */
        private boolean traceRequestBody = false;
    
        /**
         * Flag to say that path elements past the first semicolon can be dropped.
         */
        private boolean removeSemicolonContent = true;
    
        /**
         * Flag to indicate whether to decode the matched URL or use it as is.
         */
        private boolean decodeUrl = true;
    
        /**
         * List of sensitive headers that are not passed to downstream requests. Defaults to a
         * "safe" set of headers that commonly contain user credentials. It's OK to remove
         * those from the list if the downstream service is part of the same system as the
         * proxy, so they are sharing authentication data. If using a physical URL outside
         * your own domain, then generally it would be a bad idea to leak user credentials.
         */
        private Set<String> sensitiveHeaders = new LinkedHashSet<>(
                Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
    
        /**
         * Flag to say whether the hostname for ssl connections should be verified or not.
         * Default is true. This should only be used in test setups!
         */
        private boolean sslHostnameValidationEnabled = true;
    
        private ExecutionIsolationStrategy ribbonIsolationStrategy = SEMAPHORE;
    
        private HystrixSemaphore semaphore = new HystrixSemaphore();
    
        private HystrixThreadPool threadPool = new HystrixThreadPool();
    
        /**
         * Setting for SendResponseFilter to conditionally set Content-Length header.
         */
        private boolean setContentLength = false;
    
        /**
         * Setting for SendResponseFilter to conditionally include X-Zuul-Debug-Header header.
         */
        private boolean includeDebugHeader = false;
    
        /**
         * Setting for SendResponseFilter for the initial stream buffer size.
         */
        private int initialStreamBufferSize = 8192;
    
        public Set<String> getIgnoredHeaders() {
            Set<String> ignoredHeaders = new LinkedHashSet<>(this.ignoredHeaders);
            if (ClassUtils.isPresent(
                    "org.springframework.security.config.annotation.web.WebSecurityConfigurer",
                    null) && Collections.disjoint(ignoredHeaders, SECURITY_HEADERS)
                    && ignoreSecurityHeaders) {
                // Allow Spring Security in the gateway to control these headers
                ignoredHeaders.addAll(SECURITY_HEADERS);
            }
            return ignoredHeaders;
        }
    
        public void setIgnoredHeaders(Set<String> ignoredHeaders) {
            this.ignoredHeaders.addAll(ignoredHeaders);
        }
    
        @PostConstruct
        public void init() {
            for (Entry<String, ZuulRoute> entry : this.routes.entrySet()) {
                ZuulRoute value = entry.getValue();
                if (!StringUtils.hasText(value.getLocation())) {
                    value.serviceId = entry.getKey();
                }
                if (!StringUtils.hasText(value.getId())) {
                    value.id = entry.getKey();
                }
                if (!StringUtils.hasText(value.getPath())) {
                    value.path = "/" + entry.getKey() + "/**";
                }
            }
        }
    
        public String getServletPattern() {
            String path = this.servletPath;
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (!path.contains("*")) {
                path = path.endsWith("/") ? (path + "*") : (path + "/*");
            }
            return path;
        }
    
        public String getPrefix() {
            return prefix;
        }
    
        public void setPrefix(String prefix) {
            this.prefix = prefix;
        }
    
        public boolean isStripPrefix() {
            return stripPrefix;
        }
    
        public void setStripPrefix(boolean stripPrefix) {
            this.stripPrefix = stripPrefix;
        }
    
        public Boolean getRetryable() {
            return retryable;
        }
    
        public void setRetryable(Boolean retryable) {
            this.retryable = retryable;
        }
    
        public Map<String, ZuulRoute> getRoutes() {
            return routes;
        }
    
        public void setRoutes(Map<String, ZuulRoute> routes) {
            this.routes = routes;
        }
    
        public boolean isAddProxyHeaders() {
            return addProxyHeaders;
        }
    
        public void setAddProxyHeaders(boolean addProxyHeaders) {
            this.addProxyHeaders = addProxyHeaders;
        }
    
        public boolean isAddHostHeader() {
            return addHostHeader;
        }
    
        public void setAddHostHeader(boolean addHostHeader) {
            this.addHostHeader = addHostHeader;
        }
    
        public Set<String> getIgnoredServices() {
            return ignoredServices;
        }
    
        public void setIgnoredServices(Set<String> ignoredServices) {
            this.ignoredServices = ignoredServices;
        }
    
        public Set<String> getIgnoredPatterns() {
            return ignoredPatterns;
        }
    
        public void setIgnoredPatterns(Set<String> ignoredPatterns) {
            this.ignoredPatterns = ignoredPatterns;
        }
    
        public boolean isIgnoreSecurityHeaders() {
            return ignoreSecurityHeaders;
        }
    
        public void setIgnoreSecurityHeaders(boolean ignoreSecurityHeaders) {
            this.ignoreSecurityHeaders = ignoreSecurityHeaders;
        }
    
        public boolean isForceOriginalQueryStringEncoding() {
            return forceOriginalQueryStringEncoding;
        }
    
        public void setForceOriginalQueryStringEncoding(
                boolean forceOriginalQueryStringEncoding) {
            this.forceOriginalQueryStringEncoding = forceOriginalQueryStringEncoding;
        }
    
        public String getServletPath() {
            return servletPath;
        }
    
        public void setServletPath(String servletPath) {
            this.servletPath = servletPath;
        }
    
        public boolean isIgnoreLocalService() {
            return ignoreLocalService;
        }
    
        public void setIgnoreLocalService(boolean ignoreLocalService) {
            this.ignoreLocalService = ignoreLocalService;
        }
    
        public Host getHost() {
            return host;
        }
    
        public void setHost(Host host) {
            this.host = host;
        }
    
        public boolean isTraceRequestBody() {
            return traceRequestBody;
        }
    
        public void setTraceRequestBody(boolean traceRequestBody) {
            this.traceRequestBody = traceRequestBody;
        }
    
        public boolean isRemoveSemicolonContent() {
            return removeSemicolonContent;
        }
    
        public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
            this.removeSemicolonContent = removeSemicolonContent;
        }
    
        public boolean isDecodeUrl() {
            return decodeUrl;
        }
    
        public void setDecodeUrl(boolean decodeUrl) {
            this.decodeUrl = decodeUrl;
        }
    
        public Set<String> getSensitiveHeaders() {
            return sensitiveHeaders;
        }
    
        public void setSensitiveHeaders(Set<String> sensitiveHeaders) {
            this.sensitiveHeaders = sensitiveHeaders;
        }
    
        public boolean isSslHostnameValidationEnabled() {
            return sslHostnameValidationEnabled;
        }
    
        public void setSslHostnameValidationEnabled(boolean sslHostnameValidationEnabled) {
            this.sslHostnameValidationEnabled = sslHostnameValidationEnabled;
        }
    
        public ExecutionIsolationStrategy getRibbonIsolationStrategy() {
            return ribbonIsolationStrategy;
        }
    
        public void setRibbonIsolationStrategy(
                ExecutionIsolationStrategy ribbonIsolationStrategy) {
            this.ribbonIsolationStrategy = ribbonIsolationStrategy;
        }
    
        public HystrixSemaphore getSemaphore() {
            return semaphore;
        }
    
        public void setSemaphore(HystrixSemaphore semaphore) {
            this.semaphore = semaphore;
        }
    
        public HystrixThreadPool getThreadPool() {
            return threadPool;
        }
    
        public void setThreadPool(HystrixThreadPool threadPool) {
            this.threadPool = threadPool;
        }
    
        public boolean isSetContentLength() {
            return setContentLength;
        }
    
        public void setSetContentLength(boolean setContentLength) {
            this.setContentLength = setContentLength;
        }
    
        public boolean isIncludeDebugHeader() {
            return includeDebugHeader;
        }
    
        public void setIncludeDebugHeader(boolean includeDebugHeader) {
            this.includeDebugHeader = includeDebugHeader;
        }
    
        public int getInitialStreamBufferSize() {
            return initialStreamBufferSize;
        }
    
        public void setInitialStreamBufferSize(int initialStreamBufferSize) {
            this.initialStreamBufferSize = initialStreamBufferSize;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            ZuulProperties that = (ZuulProperties) o;
            return addHostHeader == that.addHostHeader
                    && addProxyHeaders == that.addProxyHeaders
                    && forceOriginalQueryStringEncoding == that.forceOriginalQueryStringEncoding
                    && Objects.equals(host, that.host)
                    && Objects.equals(ignoredHeaders, that.ignoredHeaders)
                    && Objects.equals(ignoredPatterns, that.ignoredPatterns)
                    && Objects.equals(ignoredServices, that.ignoredServices)
                    && ignoreLocalService == that.ignoreLocalService
                    && ignoreSecurityHeaders == that.ignoreSecurityHeaders
                    && Objects.equals(prefix, that.prefix)
                    && removeSemicolonContent == that.removeSemicolonContent
                    && Objects.equals(retryable, that.retryable)
                    && Objects.equals(ribbonIsolationStrategy, that.ribbonIsolationStrategy)
                    && Objects.equals(routes, that.routes)
                    && Objects.equals(semaphore, that.semaphore)
                    && Objects.equals(sensitiveHeaders, that.sensitiveHeaders)
                    && Objects.equals(servletPath, that.servletPath)
                    && sslHostnameValidationEnabled == that.sslHostnameValidationEnabled
                    && stripPrefix == that.stripPrefix
                    && setContentLength == that.setContentLength
                    && includeDebugHeader == that.includeDebugHeader
                    && initialStreamBufferSize == that.initialStreamBufferSize
                    && Objects.equals(threadPool, that.threadPool)
                    && traceRequestBody == that.traceRequestBody;
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(addHostHeader, addProxyHeaders,
                    forceOriginalQueryStringEncoding, host, ignoredHeaders, ignoredPatterns,
                    ignoredServices, ignoreLocalService, ignoreSecurityHeaders, prefix,
                    removeSemicolonContent, retryable, ribbonIsolationStrategy, routes,
                    semaphore, sensitiveHeaders, servletPath, sslHostnameValidationEnabled,
                    stripPrefix, threadPool, traceRequestBody, setContentLength,
                    includeDebugHeader, initialStreamBufferSize);
        }
    
        @Override
        public String toString() {
            return new StringBuilder("ZuulProperties{").append("prefix='").append(prefix)
                    .append("', ").append("stripPrefix=").append(stripPrefix).append(", ")
                    .append("retryable=").append(retryable).append(", ").append("routes=")
                    .append(routes).append(", ").append("addProxyHeaders=")
                    .append(addProxyHeaders).append(", ").append("addHostHeader=")
                    .append(addHostHeader).append(", ").append("ignoredServices=")
                    .append(ignoredServices).append(", ").append("ignoredPatterns=")
                    .append(ignoredPatterns).append(", ").append("ignoredHeaders=")
                    .append(ignoredHeaders).append(", ").append("ignoreSecurityHeaders=")
                    .append(ignoreSecurityHeaders).append(", ")
                    .append("forceOriginalQueryStringEncoding=")
                    .append(forceOriginalQueryStringEncoding).append(", ")
                    .append("servletPath='").append(servletPath).append("', ")
                    .append("ignoreLocalService=").append(ignoreLocalService).append(", ")
                    .append("host=").append(host).append(", ").append("traceRequestBody=")
                    .append(traceRequestBody).append(", ").append("removeSemicolonContent=")
                    .append(removeSemicolonContent).append(", ").append("sensitiveHeaders=")
                    .append(sensitiveHeaders).append(", ")
                    .append("sslHostnameValidationEnabled=")
                    .append(sslHostnameValidationEnabled).append(", ")
                    .append("ribbonIsolationStrategy=").append(ribbonIsolationStrategy)
                    .append(", ").append("semaphore=").append(semaphore).append(", ")
                    .append("threadPool=").append(threadPool).append(", ")
                    .append("setContentLength=").append(setContentLength).append(", ")
                    .append("includeDebugHeader=").append(includeDebugHeader).append(", ")
                    .append("initialStreamBufferSize=").append(initialStreamBufferSize)
                    .append(", ").append("}").toString();
        }
    
        /**
         * Represents a Zuul route.
         */
        public static class ZuulRoute {
    
            /**
             * The ID of the route (the same as its map key by default).
             */
            private String id;
    
            /**
             * The path (pattern) for the route, e.g. /foo/**.
             */
            private String path;
    
            /**
             * The service ID (if any) to map to this route. You can specify a physical URL or
             * a service, but not both.
             */
            private String serviceId;
    
            /**
             * A full physical URL to map to the route. An alternative is to use a service ID
             * and service discovery to find the physical address.
             */
            private String url;
    
            /**
             * Flag to determine whether the prefix for this route (the path, minus pattern
             * patcher) should be stripped before forwarding.
             */
            private boolean stripPrefix = true;
    
            /**
             * Flag to indicate that this route should be retryable (if supported). Generally
             * retry requires a service ID and ribbon.
             */
            private Boolean retryable;
    
            /**
             * List of sensitive headers that are not passed to downstream requests. Defaults
             * to a "safe" set of headers that commonly contain user credentials. It's OK to
             * remove those from the list if the downstream service is part of the same system
             * as the proxy, so they are sharing authentication data. If using a physical URL
             * outside your own domain, then generally it would be a bad idea to leak user
             * credentials.
             */
            private Set<String> sensitiveHeaders = new LinkedHashSet<>();
    
            private boolean customSensitiveHeaders = false;
    
            public ZuulRoute() {
            }
    
            public ZuulRoute(String id, String path, String serviceId, String url,
                    boolean stripPrefix, Boolean retryable, Set<String> sensitiveHeaders) {
                this.id = id;
                this.path = path;
                this.serviceId = serviceId;
                this.url = url;
                this.stripPrefix = stripPrefix;
                this.retryable = retryable;
                this.sensitiveHeaders = sensitiveHeaders;
                this.customSensitiveHeaders = sensitiveHeaders != null;
            }
    
            public ZuulRoute(String text) {
                String location = null;
                String path = text;
                if (text.contains("=")) {
                    String[] values = StringUtils
                            .trimArrayElements(StringUtils.split(text, "="));
                    location = values[1];
                    path = values[0];
                }
                this.id = extractId(path);
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
                setLocation(location);
                this.path = path;
            }
    
            public ZuulRoute(String path, String location) {
                this.id = extractId(path);
                this.path = path;
                setLocation(location);
            }
    
            public String getLocation() {
                if (StringUtils.hasText(this.url)) {
                    return this.url;
                }
                return this.serviceId;
            }
    
            public void setLocation(String location) {
                if (location != null
                        && (location.startsWith("http:") || location.startsWith("https:"))) {
                    this.url = location;
                }
                else {
                    this.serviceId = location;
                }
            }
    
            private String extractId(String path) {
                path = path.startsWith("/") ? path.substring(1) : path;
                path = path.replace("/*", "").replace("*", "");
                return path;
            }
    
            public Route getRoute(String prefix) {
                return new Route(this.id, this.path, getLocation(), prefix, this.retryable,
                        isCustomSensitiveHeaders() ? this.sensitiveHeaders : null,
                        this.stripPrefix);
            }
    
            public boolean isCustomSensitiveHeaders() {
                return this.customSensitiveHeaders;
            }
    
            public void setCustomSensitiveHeaders(boolean customSensitiveHeaders) {
                this.customSensitiveHeaders = customSensitiveHeaders;
            }
    
            public String getId() {
                return id;
            }
    
            public void setId(String id) {
                this.id = id;
            }
    
            public String getPath() {
                return path;
            }
    
            public void setPath(String path) {
                this.path = path;
            }
    
            public String getServiceId() {
                return serviceId;
            }
    
            public void setServiceId(String serviceId) {
                this.serviceId = serviceId;
            }
    
            public String getUrl() {
                return url;
            }
    
            public void setUrl(String url) {
                this.url = url;
            }
    
            public boolean isStripPrefix() {
                return stripPrefix;
            }
    
            public void setStripPrefix(boolean stripPrefix) {
                this.stripPrefix = stripPrefix;
            }
    
            public Boolean getRetryable() {
                return retryable;
            }
    
            public void setRetryable(Boolean retryable) {
                this.retryable = retryable;
            }
    
            public Set<String> getSensitiveHeaders() {
                return sensitiveHeaders;
            }
    
            public void setSensitiveHeaders(Set<String> headers) {
                this.customSensitiveHeaders = true;
                this.sensitiveHeaders = new LinkedHashSet<>(headers);
            }
    
            @Override
            public boolean equals(Object o) {
                if (this == o) {
                    return true;
                }
                if (o == null || getClass() != o.getClass()) {
                    return false;
                }
                ZuulRoute that = (ZuulRoute) o;
                return customSensitiveHeaders == that.customSensitiveHeaders
                        && Objects.equals(id, that.id) && Objects.equals(path, that.path)
                        && Objects.equals(retryable, that.retryable)
                        && Objects.equals(sensitiveHeaders, that.sensitiveHeaders)
                        && Objects.equals(serviceId, that.serviceId)
                        && stripPrefix == that.stripPrefix && Objects.equals(url, that.url);
            }
    
            @Override
            public int hashCode() {
                return Objects.hash(customSensitiveHeaders, id, path, retryable,
                        sensitiveHeaders, serviceId, stripPrefix, url);
            }
    
            @Override
            public String toString() {
                return new StringBuilder("ZuulRoute{").append("id='").append(id).append("', ")
                        .append("path='").append(path).append("', ").append("serviceId='")
                        .append(serviceId).append("', ").append("url='").append(url)
                        .append("', ").append("stripPrefix=").append(stripPrefix).append(", ")
                        .append("retryable=").append(retryable).append(", ")
                        .append("sensitiveHeaders=").append(sensitiveHeaders).append(", ")
                        .append("customSensitiveHeaders=").append(customSensitiveHeaders)
                        .append(", ").append("}").toString();
            }
    
        }
    
        /**
         * Represents a host.
         */
        public static class Host {
    
            /**
             * The maximum number of total connections the proxy can hold open to backends.
             */
            private int maxTotalConnections = 200;
    
            /**
             * The maximum number of connections that can be used by a single route.
             */
            private int maxPerRouteConnections = 20;
    
            /**
             * The socket timeout in millis. Defaults to 10000.
             */
            private int socketTimeoutMillis = 10000;
    
            /**
             * The connection timeout in millis. Defaults to 2000.
             */
            private int connectTimeoutMillis = 2000;
    
            /**
             * The timeout in milliseconds used when requesting a connection from the
             * connection manager. Defaults to -1, undefined use the system default.
             */
            private int connectionRequestTimeoutMillis = -1;
    
            /**
             * The lifetime for the connection pool.
             */
            private long timeToLive = -1;
    
            /**
             * The time unit for timeToLive.
             */
            private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
    
            public Host() {
            }
    
            public Host(int maxTotalConnections, int maxPerRouteConnections,
                    int socketTimeoutMillis, int connectTimeoutMillis, long timeToLive,
                    TimeUnit timeUnit) {
                this.maxTotalConnections = maxTotalConnections;
                this.maxPerRouteConnections = maxPerRouteConnections;
                this.socketTimeoutMillis = socketTimeoutMillis;
                this.connectTimeoutMillis = connectTimeoutMillis;
                this.timeToLive = timeToLive;
                this.timeUnit = timeUnit;
            }
    
            public int getMaxTotalConnections() {
                return maxTotalConnections;
            }
    
            public void setMaxTotalConnections(int maxTotalConnections) {
                this.maxTotalConnections = maxTotalConnections;
            }
    
            public int getMaxPerRouteConnections() {
                return maxPerRouteConnections;
            }
    
            public void setMaxPerRouteConnections(int maxPerRouteConnections) {
                this.maxPerRouteConnections = maxPerRouteConnections;
            }
    
            public int getSocketTimeoutMillis() {
                return socketTimeoutMillis;
            }
    
            public void setSocketTimeoutMillis(int socketTimeoutMillis) {
                this.socketTimeoutMillis = socketTimeoutMillis;
            }
    
            public int getConnectTimeoutMillis() {
                return connectTimeoutMillis;
            }
    
            public void setConnectTimeoutMillis(int connectTimeoutMillis) {
                this.connectTimeoutMillis = connectTimeoutMillis;
            }
    
            public int getConnectionRequestTimeoutMillis() {
                return connectionRequestTimeoutMillis;
            }
    
            public void setConnectionRequestTimeoutMillis(
                    int connectionRequestTimeoutMillis) {
                this.connectionRequestTimeoutMillis = connectionRequestTimeoutMillis;
            }
    
            public long getTimeToLive() {
                return timeToLive;
            }
    
            public void setTimeToLive(long timeToLive) {
                this.timeToLive = timeToLive;
            }
    
            public TimeUnit getTimeUnit() {
                return timeUnit;
            }
    
            public void setTimeUnit(TimeUnit timeUnit) {
                this.timeUnit = timeUnit;
            }
    
            @Override
            public boolean equals(Object o) {
                if (this == o) {
                    return true;
                }
                if (o == null || getClass() != o.getClass()) {
                    return false;
                }
                Host host = (Host) o;
                return maxTotalConnections == host.maxTotalConnections
                        && maxPerRouteConnections == host.maxPerRouteConnections
                        && socketTimeoutMillis == host.socketTimeoutMillis
                        && connectTimeoutMillis == host.connectTimeoutMillis
                        && connectionRequestTimeoutMillis == host.connectionRequestTimeoutMillis
                        && timeToLive == host.timeToLive && timeUnit == host.timeUnit;
            }
    
            @Override
            public int hashCode() {
                return Objects.hash(maxTotalConnections, maxPerRouteConnections,
                        socketTimeoutMillis, connectTimeoutMillis,
                        connectionRequestTimeoutMillis, timeToLive, timeUnit);
            }
    
            @Override
            public String toString() {
                return new ToStringCreator(this)
                        .append("maxTotalConnections", maxTotalConnections)
                        .append("maxPerRouteConnections", maxPerRouteConnections)
                        .append("socketTimeoutMillis", socketTimeoutMillis)
                        .append("connectTimeoutMillis", connectTimeoutMillis)
                        .append("connectionRequestTimeoutMillis",
                                connectionRequestTimeoutMillis)
                        .append("timeToLive", timeToLive).append("timeUnit", timeUnit)
                        .toString();
            }
    
        }
    
        /**
         * Represents Hystrix Sempahores.
         */
        public static class HystrixSemaphore {
    
            /**
             * The maximum number of total semaphores for Hystrix.
             */
            private int maxSemaphores = 100;
    
            public HystrixSemaphore() {
            }
    
            public HystrixSemaphore(int maxSemaphores) {
                this.maxSemaphores = maxSemaphores;
            }
    
            public int getMaxSemaphores() {
                return maxSemaphores;
            }
    
            public void setMaxSemaphores(int maxSemaphores) {
                this.maxSemaphores = maxSemaphores;
            }
    
            @Override
            public boolean equals(Object o) {
                if (this == o) {
                    return true;
                }
                if (o == null || getClass() != o.getClass()) {
                    return false;
                }
                HystrixSemaphore that = (HystrixSemaphore) o;
                return maxSemaphores == that.maxSemaphores;
            }
    
            @Override
            public int hashCode() {
                return Objects.hash(maxSemaphores);
            }
    
            @Override
            public String toString() {
                final StringBuilder sb = new StringBuilder("HystrixSemaphore{");
                sb.append("maxSemaphores=").append(maxSemaphores);
                sb.append('}');
                return sb.toString();
            }
    
        }
    
        /**
         * Represents Hystrix ThreadPool.
         */
        public static class HystrixThreadPool {
    
            /**
             * Flag to determine whether RibbonCommands should use separate thread pools for
             * hystrix. By setting to true, RibbonCommands will be executed in a hystrix's
             * thread pool that it is associated with. Each RibbonCommand will be associated
             * with a thread pool according to its commandKey (serviceId). As default, all
             * commands will be executed in a single thread pool whose threadPoolKey is
             * "RibbonCommand". This property is only applicable when using THREAD as
             * ribbonIsolationStrategy
             */
            private boolean useSeparateThreadPools = false;
    
            /**
             * A prefix for HystrixThreadPoolKey of hystrix's thread pool that is allocated to
             * each service Id. This property is only applicable when using THREAD as
             * ribbonIsolationStrategy and useSeparateThreadPools = true
             */
            private String threadPoolKeyPrefix = "";
    
            public boolean isUseSeparateThreadPools() {
                return useSeparateThreadPools;
            }
    
            public void setUseSeparateThreadPools(boolean useSeparateThreadPools) {
                this.useSeparateThreadPools = useSeparateThreadPools;
            }
    
            public String getThreadPoolKeyPrefix() {
                return threadPoolKeyPrefix;
            }
    
            public void setThreadPoolKeyPrefix(String threadPoolKeyPrefix) {
                this.threadPoolKeyPrefix = threadPoolKeyPrefix;
            }
    
        }
    
    }
    View Code

     这里只关注和Zuul流程相关的几个重要的Bean:

    1. 程序入口

    1. ZuulHandlerMapping, 相当于自定义SpringMVC的HandlerMapping
    2. ZuulController,相当于自定义Controller,继承自org.springframework.web.servlet.mvc.Controller。内部将处理逻辑委托给ZuulServlet,这种方式走的是SpringMVC的处理机制,只不过内部将出炉逻辑委托给ZuulServlet。
    3. ZuulServlet,处理代码的Servlet,继承自HttpServlet。

      上面1、2、3 方式注入到Spring之后,相当于增加了另一套经过SpringMVC的访问机制,和我们自定义的@GetMapping 是平级的一套机制。

    4. ServletRegistrationBean 注册的方式,将ZuulServlet 到tomcat的servlet列表。相当于和SpringMVC的DispatcherServlet 平级的一个Servlet, 调用的时候先过该Servlet,路由匹配失败后走SpringMVC的默认的DispatcherServlet 。

    ​    上面1,2,3,4 是zuul 拦截程序的入口。一种依赖于SpringMVC,一种相当于走Zuul的自己的路径,默认是/zuul, 可以自己通过zuul.servletPath 进行配置。参考:org.springframework.cloud.netflix.zuul.filters.ZuulProperties

    2. RouteLocator 路由查询器

      可以认为这部分是进行路由匹配的一系列Locator。包括一系列根据服务名替换路由等规则是走的这里的配置。 其实根本是读的org.springframework.cloud.netflix.zuul.filters.ZuulProperties#routes 维护的信息。

      org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration#discoveryRouteLocator 注入一个Discover 路由器, 这个内部维护了ZuulProperties 解析的路由信息。               org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration#primaryRouteLocator 这里注入一个CompositeRouteLocator 对象作为Primary的路由器。也就是作为路径查找器供后面使用。

    package org.springframework.cloud.netflix.zuul.filters;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    
    import org.springframework.core.annotation.AnnotationAwareOrderComparator;
    import org.springframework.util.Assert;
    
    /**
     * RouteLocator that composes multiple RouteLocators.
     *
     * @author Johannes Edmeier
     *
     */
    public class CompositeRouteLocator implements RefreshableRouteLocator {
    
        private final Collection<? extends RouteLocator> routeLocators;
    
        private ArrayList<RouteLocator> rl;
    
        public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {
            Assert.notNull(routeLocators, "'routeLocators' must not be null");
            rl = new ArrayList<>(routeLocators);
            AnnotationAwareOrderComparator.sort(rl);
            this.routeLocators = rl;
        }
    
        @Override
        public Collection<String> getIgnoredPaths() {
            List<String> ignoredPaths = new ArrayList<>();
            for (RouteLocator locator : routeLocators) {
                ignoredPaths.addAll(locator.getIgnoredPaths());
            }
            return ignoredPaths;
        }
    
        @Override
        public List<Route> getRoutes() {
            List<Route> route = new ArrayList<>();
            for (RouteLocator locator : routeLocators) {
                route.addAll(locator.getRoutes());
            }
            return route;
        }
    
        @Override
        public Route getMatchingRoute(String path) {
            for (RouteLocator locator : routeLocators) {
                Route route = locator.getMatchingRoute(path);
                if (route != null) {
                    return route;
                }
            }
            return null;
        }
    
        @Override
        public void refresh() {
            for (RouteLocator locator : routeLocators) {
                if (locator instanceof RefreshableRouteLocator) {
                    ((RefreshableRouteLocator) locator).refresh();
                }
            }
        }
    
    }
    View Code

      在org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping#setDirty 会调用上面的refresh 方法。

        public void setDirty(boolean dirty) {
            this.dirty = dirty;
            if (this.routeLocator instanceof RefreshableRouteLocator) {
                ((RefreshableRouteLocator) this.routeLocator).refresh();
            }
        }

     refresh 调用到: org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator#locateRoutes

        @Override
        protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
            LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
            routesMap.putAll(super.locateRoutes());
            if (this.discovery != null) {
                Map<String, ZuulRoute> staticServices = new LinkedHashMap<>();
                for (ZuulRoute route : routesMap.values()) {
                    String serviceId = route.getServiceId();
                    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();
                String[] ignored = this.properties.getIgnoredServices()
                        .toArray(new String[0]);
                for (String serviceId : services) {
                    // Ignore specifically ignored services and those that were manually
                    // configured
                    String key = "/" + mapRouteToService(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);
                        }
                    }
                    if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
                            && !routesMap.containsKey(key)) {
                        // Not ignored
                        routesMap.put(key, new ZuulRoute(key, serviceId));
                    }
                }
            }
            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);
            }
            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;
        }
    View Code

      这里相当于是刷新org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator#routes 路由信息,可以看到上面代码会针对每个服务也生成一个路由信息(/cloud-payment-service/** 其path不满足 /serviceName/**, 所以又生成了一个路由规则)。最终生成的路由信息如下:

    3. filter 用于执行处理流程的一系列过滤器。

    1. FormBodyWrapperFilter、ServletDetectionFilter、SendResponseFilter 一系列的Filter, com.netflix.zuul.ZuulFilter#filterType 方法标记其处理的类型。从源码org.springframework.cloud.netflix.zuul.filters.support.FilterConstants 也可以看出其包含的filter 类型包括:

        /**
         * {@link ZuulFilter#filterType()} error type.
         */
        public static final String ERROR_TYPE = "error";
    
        /**
         * {@link ZuulFilter#filterType()} post type.
         */
        public static final String POST_TYPE = "post";
    
        /**
         * {@link ZuulFilter#filterType()} pre type.
         */
        public static final String PRE_TYPE = "pre";
    
        /**
         * {@link ZuulFilter#filterType()} route type.
         */
        public static final String ROUTE_TYPE = "route";

    2. 注入的重要的filter

    前置过滤器

    - `ServletDetectionFilter`:检测请求是否通过Spring调度程序。使用键`FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY`设置布尔值。
    - `FormBodyWrapperFilter`:解析表单数据,并为下游请求重新编码它。
    - `DebugFilter`:如果设置`debug`请求参数,则此过滤器将`RequestContext.setDebugRouting()`和`RequestContext.setDebugRequest()`设置为true。

    路由过滤器

    - `SendForwardFilter`:此过滤器使用Servlet `RequestDispatcher`转发请求。转发位置存储在`RequestContext`属性`FilterConstants.FORWARD_TO_KEY`中。这对于转发到当前应用程序中的端点很有用。

    过滤器:

    - `SendResponseFilter`:将代理请求的响应写入当前响应。

    错误过滤器:

    - `SendErrorFilter`:如果`RequestContext.getThrowable()`不为null,则转发到/错误(默认情况下)。可以通过设置`error.path`属性来更改默认转发路径(`/error`)。

    3. org.springframework.cloud.netflix.zuul.ZuulFilterInitializer 是维护ZuulFilter的一个重要类,自动注入容器中的ZuulFilter 对象,然后进行分类后维护到com.netflix.zuul.FilterLoader 对象内,相关源码:

    (1) ZuulFilterInitializer

    public class ZuulFilterInitializer {
    
        private static final Log log = LogFactory.getLog(ZuulFilterInitializer.class);
    
        private final Map<String, ZuulFilter> filters;
    
        private final CounterFactory counterFactory;
    
        private final TracerFactory tracerFactory;
    
        private final FilterLoader filterLoader;
    
        private final FilterRegistry filterRegistry;
    
        public ZuulFilterInitializer(Map<String, ZuulFilter> filters,
                CounterFactory counterFactory, TracerFactory tracerFactory,
                FilterLoader filterLoader, FilterRegistry filterRegistry) {
            this.filters = filters;
            this.counterFactory = counterFactory;
            this.tracerFactory = tracerFactory;
            this.filterLoader = filterLoader;
            this.filterRegistry = filterRegistry;
        }
    
        @PostConstruct
        public void contextInitialized() {
            log.info("Starting filter initializer");
    
            TracerFactory.initialize(tracerFactory);
            CounterFactory.initialize(counterFactory);
    
            for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
                filterRegistry.put(entry.getKey(), entry.getValue());
            }
        }
        ...
    View Code

    (2) com.netflix.zuul.FilterLoader

    public class FilterLoader {
        final static FilterLoader INSTANCE = new FilterLoader();
    
        private static final Logger LOG = LoggerFactory.getLogger(FilterLoader.class);
    
        private final ConcurrentHashMap<String, Long> filterClassLastModified = new ConcurrentHashMap<String, Long>();
        private final ConcurrentHashMap<String, String> filterClassCode = new ConcurrentHashMap<String, String>();
        private final ConcurrentHashMap<String, String> filterCheck = new ConcurrentHashMap<String, String>();
        private final ConcurrentHashMap<String, List<ZuulFilter>> hashFiltersByType = new ConcurrentHashMap<String, List<ZuulFilter>>();
        ...
    View Code

    最后维护到hashFiltersByType 的filters 如下:

     3. 调用原理

    1. 前置

      filter 是zuul 处理流程的重要组件。ZuulServlet 是程序入口;ZuulFilter 是程序处理的逻辑,以链条的设计模式进行调用;其中RouteLocator 在Filter 中发挥作用,用于匹配解析相关的路由,然后存到RequestContext, RequestContext 继承自ConcurrentHashMap, 用于维护当前请求的一些上下文参数, 内部用threadLocal 维护当前请求的当前RequestContext。整个ZullFilter 链条 用RequestContext 进行数据传递,且key 在常量类中定义。

      参考: com.netflix.zuul.context.RequestContext 和 org.springframework.cloud.netflix.zuul.filters.support.FilterConstants 类。

    不管哪种方式的调用最终都会经过ZuulServlet。ZuulServlet 代码如下:

    public class ZuulServlet extends HttpServlet {
    
        private static final long serialVersionUID = -3374242278843351500L;
        private ZuulRunner 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);
        }
    
        @Override
        public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
            try {
                init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
    
                // Marks this request as having passed through the "Zuul engine", as opposed to servlets
                // explicitly bound in web.xml, for which requests will not have the same data attached
                RequestContext context = RequestContext.getCurrentContext();
                context.setZuulEngineRan();
    
                try {
                    preRoute();
                } catch (ZuulException e) {
                    error(e);
                    postRoute();
                    return;
                }
                try {
                    route();
                } catch (ZuulException e) {
                    error(e);
                    postRoute();
                    return;
                }
                try {
                    postRoute();
                } catch (ZuulException e) {
                    error(e);
                    return;
                }
    
            } catch (Throwable e) {
                error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
            } finally {
                RequestContext.getCurrentContext().unset();
            }
        }
    
        /**
         * executes "post" ZuulFilters
         *
         * @throws ZuulException
         */
        void postRoute() throws ZuulException {
            zuulRunner.postRoute();
        }
    
        /**
         * executes "route" filters
         *
         * @throws ZuulException
         */
        void route() throws ZuulException {
            zuulRunner.route();
        }
    
        /**
         * executes "pre" filters
         *
         * @throws ZuulException
         */
        void preRoute() throws ZuulException {
            zuulRunner.preRoute();
        }
    
        /**
         * initializes request
         *
         * @param servletRequest
         * @param servletResponse
         */
        void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
            zuulRunner.init(servletRequest, servletResponse);
        }
    
        /**
         * sets error context info and executes "error" filters
         *
         * @param e
         */
        void error(ZuulException e) {
            RequestContext.getCurrentContext().setThrowable(e);
            zuulRunner.error();
        }
    
        @RunWith(MockitoJUnitRunner.class)
        public static class UnitTest {
    
            @Mock
            HttpServletRequest servletRequest;
            @Mock
            HttpServletResponseWrapper servletResponse;
            @Mock
            FilterProcessor processor;
            @Mock
            PrintWriter writer;
    
            @Before
            public void before() {
                MockitoAnnotations.initMocks(this);
            }
    
            @Test
            public void testProcessZuulFilter() {
    
                ZuulServlet zuulServlet = new ZuulServlet();
                zuulServlet = spy(zuulServlet);
                RequestContext context = spy(RequestContext.getCurrentContext());
    
    
                try {
                    FilterProcessor.setProcessor(processor);
                    RequestContext.testSetCurrentContext(context);
                    when(servletResponse.getWriter()).thenReturn(writer);
    
                    zuulServlet.init(servletRequest, servletResponse);
                    verify(zuulServlet, times(1)).init(servletRequest, servletResponse);
                    assertTrue(RequestContext.getCurrentContext().getRequest() instanceof HttpServletRequestWrapper);
                    assertTrue(RequestContext.getCurrentContext().getResponse() instanceof HttpServletResponseWrapper);
    
                    zuulServlet.preRoute();
                    verify(processor, times(1)).preRoute();
    
                    zuulServlet.postRoute();
                    verify(processor, times(1)).postRoute();
    //                verify(context, times(1)).unset();
    
                    zuulServlet.route();
                    verify(processor, times(1)).route();
                    RequestContext.testSetCurrentContext(null);
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
    
    
            }
        }
    
    }
    View Code

    2. /api/v1/payment/pay/getServerPort 路由原理

      这种配置方式经过SpringMVC 路由,由SpringMVC的DispatcherServlet 进行转发。调用链如下:

     接下来研究其调用过程:

      从 com.netflix.zuul.http.ZuulServlet#service 代码看出,其过程分为路由前、路由、路由后, 如果发生错误就走error类型的filter。其中RequestContext 用于整个流程中的数据共享。各种filter的运行会走到ZuulRunner。

        /**
         * sets HttpServlet request and HttpResponse
         *
         * @param servletRequest
         * @param servletResponse
         */
        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));
        }
    
        /**
         * executes "post" filterType  ZuulFilters
         *
         * @throws ZuulException
         */
        public void postRoute() throws ZuulException {
            FilterProcessor.getInstance().postRoute();
        }
    
        /**
         * executes "route" filterType  ZuulFilters
         *
         * @throws ZuulException
         */
        public void route() throws ZuulException {
            FilterProcessor.getInstance().route();
        }
    
        /**
         * executes "pre" filterType  ZuulFilters
         *
         * @throws ZuulException
         */
        public void preRoute() throws ZuulException {
            FilterProcessor.getInstance().preRoute();
        }
    
        /**
         * executes "error" filterType  ZuulFilters
         */
        public void error() {
            FilterProcessor.getInstance().error();
        }
    View Code

        继续调用到FilterProcessor相关代码

        /**
         * runs "post" filters which are called after "route" filters. ZuulExceptions from ZuulFilters are thrown.
         * Any other Throwables are caught and a ZuulException is thrown out with a 500 status code
         *
         * @throws ZuulException
         */
        public void postRoute() throws ZuulException {
            try {
                runFilters("post");
            } catch (ZuulException e) {
                throw e;
            } catch (Throwable e) {
                throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
            }
        }
    
        /**
         * runs all "error" filters. These are called only if an exception occurs. Exceptions from this are swallowed and logged so as not to bubble up.
         */
        public void error() {
            try {
                runFilters("error");
            } catch (Throwable e) {
                logger.error(e.getMessage(), e);
            }
        }
    
        /**
         * Runs all "route" filters. These filters route calls to an origin.
         *
         * @throws ZuulException if an exception occurs.
         */
        public void route() throws ZuulException {
            try {
                runFilters("route");
            } catch (ZuulException e) {
                throw e;
            } catch (Throwable e) {
                throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
            }
        }
    
        /**
         * runs all "pre" filters. These filters are run before routing to the orgin.
         *
         * @throws ZuulException
         */
        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());
            }
        }
    
        /**
         * runs all filters of the filterType sType/ Use this method within filters to run custom filters by type
         *
         * @param sType the filterType.
         * @return
         * @throws Throwable throws up an arbitrary exception
         */
        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 = list.get(i);
                    Object result = processZuulFilter(zuulFilter);
                    if (result != null && result instanceof Boolean) {
                        bResult |= ((Boolean) result);
                    }
                }
            }
            return bResult;
        }
    
        /**
         * Processes an individual ZuulFilter. This method adds Debug information. Any uncaught Thowables are caught by this method and converted to a ZuulException with a 500 status code.
         *
         * @param filter
         * @return the return value for that filter
         * @throws ZuulException
         */
        public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
    
            RequestContext ctx = RequestContext.getCurrentContext();
            boolean bDebug = ctx.debugRouting();
            final String metricPrefix = "zuul.filter-";
            long execTime = 0;
            String filterName = "";
            try {
                long ltime = System.currentTimeMillis();
                filterName = filter.getClass().getSimpleName();
                
                RequestContext copy = null;
                Object o = null;
                Throwable t = null;
    
                if (bDebug) {
                    Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
                    copy = ctx.copy();
                }
                
                ZuulFilterResult result = filter.runFilter();
                ExecutionStatus s = result.getStatus();
                execTime = System.currentTimeMillis() - ltime;
    
                switch (s) {
                    case FAILED:
                        t = result.getException();
                        ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                        break;
                    case SUCCESS:
                        o = result.getResult();
                        ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                        if (bDebug) {
                            Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
                            Debug.compareContextState(filterName, copy);
                        }
                        break;
                    default:
                        break;
                }
                
                if (t != null) throw t;
    
                usageNotifier.notify(filter, s);
                return o;
    
            } catch (Throwable e) {
                if (bDebug) {
                    Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
                }
                usageNotifier.notify(filter, ExecutionStatus.FAILED);
                if (e instanceof ZuulException) {
                    throw (ZuulException) e;
                } else {
                    ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                    throw ex;
                }
            }
        }
    View Code

        可以看到runFilters 是类似于链条模式的调用方法,遍历集合中所有的过滤器,然后逐个进行调用。

    1. preFilter

      包含6个前置处理器。

    1. org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter

    public class ServletDetectionFilter extends ZuulFilter {
    
        public ServletDetectionFilter() {
        }
    
        @Override
        public String filterType() {
            return PRE_TYPE;
        }
    
        /**
         * Must run before other filters that rely on the difference between DispatcherServlet
         * and ZuulServlet.
         */
        @Override
        public int filterOrder() {
            return SERVLET_DETECTION_FILTER_ORDER;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            if (!(request instanceof HttpServletRequestWrapper)
                    && isDispatcherServletRequest(request)) {
                ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);
            }
            else {
                ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);
            }
    
            return null;
        }
    
        private boolean isDispatcherServletRequest(HttpServletRequest request) {
            return request.getAttribute(
                    DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;
        }
    
    }
    View Code

      这个filter 判断请求是来自SpringMVC的Dispatcher还是直接来自ZuulServlet,也就是如果以/cloud-zuul/ 开头的路由这个是false,否则放的状态位为true。

      检测依据是 isDispatcherServletRequest 方法 根据request 域中是否有org.springframework.web.servlet.DispatcherServlet#WEB_APPLICATION_CONTEXT_ATTRIBUTE 相关的属性, 如果走SptingMVC 会在org.springframework.web.servlet.DispatcherServlet#doService 放置相关属性:

            // Make framework objects available to handlers and view objects.
            request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
            request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
            request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
            request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    2. org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter

        这个ilter 根据request 请求来源选择性包装,如果是来自SpringMVC 就包装,否则就不包装。

    public class Servlet30WrapperFilter extends ZuulFilter {
    
        private Field requestField = null;
    
        public Servlet30WrapperFilter() {
            this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class,
                    "req", HttpServletRequest.class);
            Assert.notNull(this.requestField,
                    "HttpServletRequestWrapper.req field not found");
            this.requestField.setAccessible(true);
        }
    
        protected Field getRequestField() {
            return this.requestField;
        }
    
        @Override
        public String filterType() {
            return PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return SERVLET_30_WRAPPER_FILTER_ORDER;
        }
    
        @Override
        public boolean shouldFilter() {
            return true; // TODO: only if in servlet 3.0 env
        }
    
        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            if (request instanceof HttpServletRequestWrapper) {
                request = (HttpServletRequest) ReflectionUtils.getField(this.requestField,
                        request);
                ctx.setRequest(new Servlet30RequestWrapper(request));
            }
            else if (RequestUtils.isDispatcherServletRequest()) {
                // If it's going through the dispatcher we need to buffer the body
                ctx.setRequest(new Servlet30RequestWrapper(request));
            }
            return null;
        }
    
    }
    View Code

    3. cn.qz.cloud.filter.PreFilter

        这是自己的前置过滤器,打印一些日志。

    4. org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter

        对于来自SpringMVC的请求并且Content-Type是multipart/form-data(文件上传)、或者Content-Type = application/x-www-form-urlencoded (表单提交)的请求进行包装。

    public class FormBodyWrapperFilter extends ZuulFilter {
    
        private FormHttpMessageConverter formHttpMessageConverter;
    
        private Field requestField;
    
        private Field servletRequestField;
    
        public FormBodyWrapperFilter() {
            this(new AllEncompassingFormHttpMessageConverter());
        }
    
        public FormBodyWrapperFilter(FormHttpMessageConverter formHttpMessageConverter) {
            this.formHttpMessageConverter = formHttpMessageConverter;
            this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class,
                    "req", HttpServletRequest.class);
            this.servletRequestField = ReflectionUtils.findField(ServletRequestWrapper.class,
                    "request", ServletRequest.class);
            Assert.notNull(this.requestField,
                    "HttpServletRequestWrapper.req field not found");
            Assert.notNull(this.servletRequestField,
                    "ServletRequestWrapper.request field not found");
            this.requestField.setAccessible(true);
            this.servletRequestField.setAccessible(true);
        }
    
        @Override
        public String filterType() {
            return PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return FORM_BODY_WRAPPER_FILTER_ORDER;
        }
    
        @Override
        public boolean shouldFilter() {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            String contentType = request.getContentType();
            // Don't use this filter on GET method
            if (contentType == null) {
                return false;
            }
            // Only use this filter for form data and only for multipart data in a
            // DispatcherServlet handler
            try {
                MediaType mediaType = MediaType.valueOf(contentType);
                return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)
                        || (isDispatcherServletRequest(request)
                                && MediaType.MULTIPART_FORM_DATA.includes(mediaType));
            }
            catch (InvalidMediaTypeException ex) {
                return false;
            }
        }
    
        private boolean isDispatcherServletRequest(HttpServletRequest request) {
            return request.getAttribute(
                    DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;
        }
    
        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            FormBodyRequestWrapper wrapper = null;
            if (request instanceof HttpServletRequestWrapper) {
                HttpServletRequest wrapped = (HttpServletRequest) ReflectionUtils
                        .getField(this.requestField, request);
                wrapper = new FormBodyRequestWrapper(wrapped);
                ReflectionUtils.setField(this.requestField, request, wrapper);
                if (request instanceof ServletRequestWrapper) {
                    ReflectionUtils.setField(this.servletRequestField, request, wrapper);
                }
            }
            else {
                wrapper = new FormBodyRequestWrapper(request);
                ctx.setRequest(wrapper);
            }
            if (wrapper != null) {
                ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
            }
            return null;
        }
    
        private class FormBodyRequestWrapper extends Servlet30RequestWrapper {
    
            private HttpServletRequest request;
    
            private volatile byte[] contentData;
    
            private MediaType contentType;
    
            private int contentLength;
    
            FormBodyRequestWrapper(HttpServletRequest request) {
                super(request);
                this.request = request;
            }
    
            @Override
            public String getContentType() {
                if (this.contentData == null) {
                    buildContentData();
                }
                return this.contentType.toString();
            }
    
            @Override
            public int getContentLength() {
                if (super.getContentLength() <= 0) {
                    return super.getContentLength();
                }
                if (this.contentData == null) {
                    buildContentData();
                }
                return this.contentLength;
            }
    
            public long getContentLengthLong() {
                return getContentLength();
            }
    
            @Override
            public ServletInputStream getInputStream() throws IOException {
                if (this.contentData == null) {
                    buildContentData();
                }
                return new ServletInputStreamWrapper(this.contentData);
            }
    
            private synchronized void buildContentData() {
                if (this.contentData != null) {
                    return;
                }
                try {
                    MultiValueMap<String, Object> builder = RequestContentDataExtractor
                            .extract(this.request);
                    FormHttpOutputMessage data = new FormHttpOutputMessage();
    
                    this.contentType = MediaType.valueOf(this.request.getContentType());
                    data.getHeaders().setContentType(this.contentType);
                    FormBodyWrapperFilter.this.formHttpMessageConverter.write(builder,
                            this.contentType, data);
                    // copy new content type including multipart boundary
                    this.contentType = data.getHeaders().getContentType();
                    byte[] input = data.getInput();
                    this.contentLength = input.length;
                    this.contentData = input;
                }
                catch (Exception e) {
                    throw new IllegalStateException("Cannot convert form data", e);
                }
            }
    
            private class FormHttpOutputMessage implements HttpOutputMessage {
    
                private HttpHeaders headers = new HttpHeaders();
    
                private ByteArrayOutputStream output = new ByteArrayOutputStream();
    
                @Override
                public HttpHeaders getHeaders() {
                    return this.headers;
                }
    
                @Override
                public OutputStream getBody() throws IOException {
                    return this.output;
                }
    
                public byte[] getInput() throws IOException {
                    this.output.flush();
                    return this.output.toByteArray();
                }
    
            }
    
        }
    
    }
    View Code

    5. org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter

    public class DebugFilter extends ZuulFilter {
    
        private static final DynamicBooleanProperty ROUTING_DEBUG = DynamicPropertyFactory
                .getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, false);
    
        private static final DynamicStringProperty DEBUG_PARAMETER = DynamicPropertyFactory
                .getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "debug");
    
        @Override
        public String filterType() {
            return PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return DEBUG_FILTER_ORDER;
        }
    
        @Override
        public boolean shouldFilter() {
            HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
            if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {
                return true;
            }
            return ROUTING_DEBUG.get();
        }
    
        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            ctx.setDebugRouting(true);
            ctx.setDebugRequest(true);
            return null;
        }
    
    }
    View Code

      debug 设置debug参数。默认是false, 不走该filter。

    6. org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter

      这个filter比较重要,会放一些路由相关的信息,从RouteLocator 获取路由信息。转发或者以服务名路由都是在这里进行提取的。提取到后面的服务名称,然后用key org.springframework.cloud.netflix.zuul.filters.support.FilterConstants#SERVICE_ID_KEY 维护到RequestContext 中。

    public class PreDecorationFilter extends ZuulFilter {
    
        private static final Log log = LogFactory.getLog(PreDecorationFilter.class);
    
        /**
         * @deprecated use {@link FilterConstants#PRE_DECORATION_FILTER_ORDER}
         */
        @Deprecated
        public static final int FILTER_ORDER = PRE_DECORATION_FILTER_ORDER;
    
        /**
         * A double slash pattern.
         */
        public static final Pattern DOUBLE_SLASH = Pattern.compile("//");
    
        private RouteLocator routeLocator;
    
        private String dispatcherServletPath;
    
        private ZuulProperties properties;
    
        private UrlPathHelper urlPathHelper = new UrlPathHelper();
    
        private ProxyRequestHelper proxyRequestHelper;
    
        public PreDecorationFilter(RouteLocator routeLocator, String dispatcherServletPath,
                ZuulProperties properties, ProxyRequestHelper proxyRequestHelper) {
            this.routeLocator = routeLocator;
            this.properties = properties;
            this.urlPathHelper
                    .setRemoveSemicolonContent(properties.isRemoveSemicolonContent());
            this.urlPathHelper.setUrlDecode(properties.isDecodeUrl());
            this.dispatcherServletPath = dispatcherServletPath;
            this.proxyRequestHelper = proxyRequestHelper;
        }
    
        @Override
        public int filterOrder() {
            return PRE_DECORATION_FILTER_ORDER;
        }
    
        @Override
        public String filterType() {
            return PRE_TYPE;
        }
    
        @Override
        public boolean shouldFilter() {
            RequestContext ctx = RequestContext.getCurrentContext();
            return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
                    && !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined
            // serviceId
        }
    
        @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(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()));
                    }
                }
            }
            else {
                log.warn("No route found for uri: " + requestURI);
                String forwardURI = getForwardUri(requestURI);
    
                ctx.set(FORWARD_TO_KEY, forwardURI);
            }
            return null;
        }
    
        /* for testing */ String getForwardUri(String requestURI) {
            // default fallback servlet is DispatcherServlet
            String fallbackPrefix = this.dispatcherServletPath;
    
            String fallBackUri = requestURI;
            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 if (this.dispatcherServletPath != null) {
                // 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 == null) ? fallBackUri
                    : fallbackPrefix + fallBackUri;
            forwardURI = DOUBLE_SLASH.matcher(forwardURI).replaceAll("/");
            return forwardURI;
        }
    
        private 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);
        }
    
        private boolean hasHeader(HttpServletRequest request, String name) {
            return StringUtils.hasLength(request.getHeader(name));
        }
    
        private 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);
            }
        }
    
        private 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;
            }
        }
    
        private URL getUrl(String target) {
            try {
                return new URL(target);
            }
            catch (MalformedURLException ex) {
                throw new IllegalStateException("Target URL is malformed", ex);
            }
        }
    
    }
    View Code

    2. routeFilter 路由处理器

    1. RibbonRoutingFilter

      使用ribbon替换服务名称为实际的IP和port, 然后用httpclient 发送请求。

    public class RibbonRoutingFilter extends ZuulFilter {
    
        private static final Log log = LogFactory.getLog(RibbonRoutingFilter.class);
    
        protected ProxyRequestHelper helper;
    
        protected RibbonCommandFactory<?> ribbonCommandFactory;
    
        protected List<RibbonRequestCustomizer> requestCustomizers;
    
        private boolean useServlet31 = true;
    
        public RibbonRoutingFilter(ProxyRequestHelper helper,
                RibbonCommandFactory<?> ribbonCommandFactory,
                List<RibbonRequestCustomizer> requestCustomizers) {
            this.helper = helper;
            this.ribbonCommandFactory = ribbonCommandFactory;
            this.requestCustomizers = requestCustomizers;
            // To support Servlet API 3.1 we need to check if getContentLengthLong exists
            // Spring 5 minimum support is 3.0, so this stays
            try {
                HttpServletRequest.class.getMethod("getContentLengthLong");
            }
            catch (NoSuchMethodException e) {
                useServlet31 = false;
            }
        }
    
        @Deprecated
        // TODO Remove in 2.1.x
        public RibbonRoutingFilter(RibbonCommandFactory<?> ribbonCommandFactory) {
            this(new ProxyRequestHelper(), ribbonCommandFactory, null);
        }
    
        /* for testing */ boolean isUseServlet31() {
            return useServlet31;
        }
    
        @Override
        public String filterType() {
            return ROUTE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return RIBBON_ROUTING_FILTER_ORDER;
        }
    
        @Override
        public boolean shouldFilter() {
            RequestContext ctx = RequestContext.getCurrentContext();
            return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
                    && ctx.sendZuulResponse());
        }
    
        @Override
        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);
            }
        }
    
        protected RibbonCommandContext buildCommandContext(RequestContext context) {
            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 && !verb.equalsIgnoreCase("GET")) {
                context.setChunkedRequestBody();
            }
    
            String serviceId = (String) context.get(SERVICE_ID_KEY);
            Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
            Object loadBalancerKey = context.get(LOAD_BALANCER_KEY);
    
            String uri = this.helper.buildZuulRequestURI(request);
    
            // remove double slashes
            uri = uri.replace("//", "/");
    
            long contentLength = useServlet31 ? request.getContentLengthLong()
                    : request.getContentLength();
    
            return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
                    requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
        }
    
        protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
            Map<String, Object> info = this.helper.debug(context.getMethod(),
                    context.getUri(), context.getHeaders(), context.getParams(),
                    context.getRequestEntity());
    
            RibbonCommand command = this.ribbonCommandFactory.create(context);
            try {
                ClientHttpResponse response = command.execute();
                this.helper.appendDebug(info, response.getRawStatusCode(),
                        response.getHeaders());
                return response;
            }
            catch (HystrixRuntimeException ex) {
                return handleException(info, ex);
            }
    
        }
    
        protected ClientHttpResponse handleException(Map<String, Object> info,
                HystrixRuntimeException ex) throws ZuulException {
            int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
            Throwable cause = ex;
            String message = ex.getFailureType().toString();
    
            ClientException clientException = findClientException(ex);
            if (clientException == null) {
                clientException = findClientException(ex.getFallbackException());
            }
    
            if (clientException != null) {
                if (clientException
                        .getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
                    statusCode = HttpStatus.SERVICE_UNAVAILABLE.value();
                }
                cause = clientException;
                message = clientException.getErrorType().toString();
            }
            info.put("status", String.valueOf(statusCode));
            throw new ZuulException(cause, "Forwarding error", statusCode, message);
        }
    
        protected ClientException findClientException(Throwable t) {
            if (t == null) {
                return null;
            }
            if (t instanceof ClientException) {
                return (ClientException) t;
            }
            return findClientException(t.getCause());
        }
    
        protected InputStream getRequestBody(HttpServletRequest request) {
            InputStream requestEntity = null;
            try {
                requestEntity = (InputStream) RequestContext.getCurrentContext()
                        .get(REQUEST_ENTITY_KEY);
                if (requestEntity == null) {
                    requestEntity = request.getInputStream();
                }
            }
            catch (IOException ex) {
                log.error("Error during getRequestBody", ex);
            }
            return requestEntity;
        }
    
        protected String getVerb(HttpServletRequest request) {
            String method = request.getMethod();
            if (method == null) {
                return "GET";
            }
            return method;
        }
    
        protected void setResponse(ClientHttpResponse resp)
                throws ClientException, IOException {
            RequestContext.getCurrentContext().set("zuulResponse", resp);
            this.helper.setResponse(resp.getRawStatusCode(),
                    resp.getBody() == null ? null : resp.getBody(), resp.getHeaders());
        }
    
    }
    View Code

      可以看出核心是创建一个RibbonCommand 对象(实际类型是HttpClientRibbonCommand),然后开始转发请求(这实际是zuul对Ribbon 做的封装)。然后会调用到HttpClientRibbonCommand 父类org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand#run 方法

        @Override
        protected ClientHttpResponse run() throws Exception {
            final RequestContext context = RequestContext.getCurrentContext();
    
            RQ request = createRequest();
            RS response;
    
            boolean retryableClient = this.client instanceof AbstractLoadBalancingClient
                    && ((AbstractLoadBalancingClient) this.client)
                            .isClientRetryable((ContextAwareRequest) request);
    
            if (retryableClient) {
                response = this.client.execute(request, config);
            }
            else {
                response = this.client.executeWithLoadBalancer(request, config);
            }
            context.set("ribbonResponse", response);
    
            // Explicitly close the HttpResponse if the Hystrix command timed out to
            // release the underlying HTTP connection held by the response.
            //
            if (this.isResponseTimedOut()) {
                if (response != null) {
                    response.close();
                }
            }
    
            return new RibbonHttpResponse(response);
        }
    View Code

        然后调用到com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig), 开始走Ribbon 进行调用。(这里之后就相当于请求交给Ribbon 去发起请求)

        public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
            LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
    
            try {
                return command.submit(
                    new ServerOperation<T>() {
                        @Override
                        public Observable<T> call(Server server) {
                            URI finalUri = reconstructURIWithServer(server, request.getUri());
                            S requestForServer = (S) request.replaceUri(finalUri);
                            try {
                                return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                            } 
                            catch (Exception e) {
                                return Observable.error(e);
                            }
                        }
                    })
                    .toBlocking()
                    .single();
            } catch (Exception e) {
                Throwable t = e.getCause();
                if (t instanceof ClientException) {
                    throw (ClientException) t;
                } else {
                    throw new ClientException(e);
                }
            }
            
        }
    View Code

    (1) finalUri 是生成最后的uri, 替换掉服务名称的uri

    (2) 继续调用org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient#execute

        public RibbonApacheHttpResponse execute(RibbonApacheHttpRequest request,
                final IClientConfig configOverride) throws Exception {
            IClientConfig config = configOverride != null ? configOverride : this.config;
            RibbonProperties ribbon = RibbonProperties.from(config);
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(ribbon.connectTimeout(this.connectTimeout))
                    .setSocketTimeout(ribbon.readTimeout(this.readTimeout))
                    .setRedirectsEnabled(ribbon.isFollowRedirects(this.followRedirects))
                    .setContentCompressionEnabled(ribbon.isGZipPayload(this.gzipPayload))
                    .build();
    
            request = getSecureRequest(request, configOverride);
            final HttpUriRequest httpUriRequest = request.toRequest(requestConfig);
            final HttpResponse httpResponse = this.delegate.execute(httpUriRequest);
            return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
        }
    View Code

      这里实际就是委托给相应的delegate 客户端去发送请求,默认走的是org.apache.http.impl.client.CloseableHttpClient#execute(org.apache.http.client.methods.HttpUriRequest) -》 位于HttpClient 包的客户端。

     

      响应回来之后org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter#setResponse 将响应记录到RequestContext 中便于后面响应结果。

    2. org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter

        这个filter 实际负责不需要负载均衡的路由的转发,也就是不走Ribbon 机制的一些路由转发。比如上面走 /guoji/ 的服务就是从这个类进行发送请求的。

    package org.springframework.cloud.netflix.zuul.filters.route;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.regex.Pattern;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    import javax.servlet.http.HttpServletRequest;
    
    import com.netflix.client.ClientException;
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.apache.http.Header;
    import org.apache.http.HttpHost;
    import org.apache.http.HttpRequest;
    import org.apache.http.HttpResponse;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.config.CookieSpecs;
    import org.apache.http.client.config.RequestConfig;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPatch;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.client.methods.HttpPut;
    import org.apache.http.conn.HttpClientConnectionManager;
    import org.apache.http.entity.ContentType;
    import org.apache.http.entity.InputStreamEntity;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.message.BasicHeader;
    import org.apache.http.message.BasicHttpEntityEnclosingRequest;
    import org.apache.http.message.BasicHttpRequest;
    
    import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
    import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
    import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
    import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
    import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
    import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.Host;
    import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;
    import org.springframework.context.ApplicationListener;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.util.LinkedMultiValueMap;
    import org.springframework.util.MultiValueMap;
    import org.springframework.util.StringUtils;
    
    import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.REQUEST_ENTITY_KEY;
    import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ROUTE_TYPE;
    import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SIMPLE_HOST_ROUTING_FILTER_ORDER;
    
    /**
     * Route {@link ZuulFilter} that sends requests to predetermined URLs via apache
     * {@link HttpClient}. URLs are found in {@link RequestContext#getRouteHost()}.
     *
     * @author Spencer Gibb
     * @author Dave Syer
     * @author Bilal Alp
     * @author Gang Li
     * @author Denys Ivano
     */
    public class SimpleHostRoutingFilter extends ZuulFilter
            implements ApplicationListener<EnvironmentChangeEvent> {
    
        private static final Log log = LogFactory.getLog(SimpleHostRoutingFilter.class);
    
        private static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("/{2,}");
    
        private final Timer connectionManagerTimer = new Timer(
                "SimpleHostRoutingFilter.connectionManagerTimer", true);
    
        private boolean sslHostnameValidationEnabled;
    
        private boolean forceOriginalQueryStringEncoding;
    
        private ProxyRequestHelper helper;
    
        private Host hostProperties;
    
        private ApacheHttpClientConnectionManagerFactory connectionManagerFactory;
    
        private ApacheHttpClientFactory httpClientFactory;
    
        private HttpClientConnectionManager connectionManager;
    
        private CloseableHttpClient httpClient;
    
        private boolean customHttpClient = false;
    
        private boolean useServlet31 = true;
    
        @Override
        @SuppressWarnings("Deprecation")
        public void onApplicationEvent(EnvironmentChangeEvent event) {
            onPropertyChange(event);
        }
    
        @Deprecated
        public void onPropertyChange(EnvironmentChangeEvent event) {
            if (!customHttpClient) {
                boolean createNewClient = false;
    
                for (String key : event.getKeys()) {
                    if (key.startsWith("zuul.host.")) {
                        createNewClient = true;
                        break;
                    }
                }
    
                if (createNewClient) {
                    try {
                        this.httpClient.close();
                    }
                    catch (IOException ex) {
                        log.error("error closing client", ex);
                    }
                    // Re-create connection manager (may be shut down on HTTP client close)
                    try {
                        this.connectionManager.shutdown();
                    }
                    catch (RuntimeException ex) {
                        log.error("error shutting down connection manager", ex);
                    }
                    this.connectionManager = newConnectionManager();
                    this.httpClient = newClient();
                }
            }
        }
    
        public SimpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties properties,
                ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
                ApacheHttpClientFactory httpClientFactory) {
            this.helper = helper;
            this.hostProperties = properties.getHost();
            this.sslHostnameValidationEnabled = properties.isSslHostnameValidationEnabled();
            this.forceOriginalQueryStringEncoding = properties
                    .isForceOriginalQueryStringEncoding();
            this.connectionManagerFactory = connectionManagerFactory;
            this.httpClientFactory = httpClientFactory;
            checkServletVersion();
        }
    
        public SimpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties properties,
                CloseableHttpClient httpClient) {
            this.helper = helper;
            this.hostProperties = properties.getHost();
            this.sslHostnameValidationEnabled = properties.isSslHostnameValidationEnabled();
            this.forceOriginalQueryStringEncoding = properties
                    .isForceOriginalQueryStringEncoding();
            this.httpClient = httpClient;
            this.customHttpClient = true;
            checkServletVersion();
        }
    
        @PostConstruct
        private void initialize() {
            if (!customHttpClient) {
                this.connectionManager = newConnectionManager();
                this.httpClient = newClient();
                this.connectionManagerTimer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        if (SimpleHostRoutingFilter.this.connectionManager == null) {
                            return;
                        }
                        SimpleHostRoutingFilter.this.connectionManager
                                .closeExpiredConnections();
                    }
                }, 30000, 5000);
            }
        }
    
        @PreDestroy
        public void stop() {
            this.connectionManagerTimer.cancel();
        }
    
        @Override
        public String filterType() {
            return ROUTE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return SIMPLE_HOST_ROUTING_FILTER_ORDER;
        }
    
        @Override
        public boolean shouldFilter() {
            return RequestContext.getCurrentContext().getRouteHost() != null
                    && RequestContext.getCurrentContext().sendZuulResponse();
        }
    
        @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 (getContentLength(request) < 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(handleException(ex));
            }
            return null;
        }
    
        protected ZuulException handleException(Exception ex) {
            int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
            Throwable cause = ex;
            String message = ex.getMessage();
    
            ClientException clientException = findClientException(ex);
    
            if (clientException != null) {
                if (clientException
                        .getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
                    statusCode = HttpStatus.SERVICE_UNAVAILABLE.value();
                }
                cause = clientException;
                message = clientException.getErrorType().toString();
            }
            return new ZuulException(cause, "Forwarding error", statusCode, message);
        }
    
        protected ClientException findClientException(Throwable t) {
            if (t == null) {
                return null;
            }
            if (t instanceof ClientException) {
                return (ClientException) t;
            }
            return findClientException(t.getCause());
        }
    
        protected void checkServletVersion() {
            // To support Servlet API 3.1 we need to check if getContentLengthLong exists
            // Spring 5 minimum support is 3.0, so this stays
            try {
                HttpServletRequest.class.getMethod("getContentLengthLong");
                useServlet31 = true;
            }
            catch (NoSuchMethodException e) {
                useServlet31 = false;
            }
        }
    
        protected void setUseServlet31(boolean useServlet31) {
            this.useServlet31 = useServlet31;
        }
    
        protected HttpClientConnectionManager getConnectionManager() {
            return connectionManager;
        }
    
        protected HttpClientConnectionManager newConnectionManager() {
            return connectionManagerFactory.newConnectionManager(
                    !this.sslHostnameValidationEnabled,
                    this.hostProperties.getMaxTotalConnections(),
                    this.hostProperties.getMaxPerRouteConnections(),
                    this.hostProperties.getTimeToLive(), this.hostProperties.getTimeUnit(),
                    null);
        }
    
        protected CloseableHttpClient newClient() {
            final RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectionRequestTimeout(
                            this.hostProperties.getConnectionRequestTimeoutMillis())
                    .setSocketTimeout(this.hostProperties.getSocketTimeoutMillis())
                    .setConnectTimeout(this.hostProperties.getConnectTimeoutMillis())
                    .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
            return httpClientFactory.createBuilder().setDefaultRequestConfig(requestConfig)
                    .setConnectionManager(this.connectionManager).disableRedirectHandling()
                    .build();
        }
    
        private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb,
                String uri, HttpServletRequest request, MultiValueMap<String, String> headers,
                MultiValueMap<String, String> params, InputStream requestEntity)
                throws Exception {
            Map<String, Object> info = this.helper.debug(verb, uri, headers, params,
                    requestEntity);
            URL host = RequestContext.getCurrentContext().getRouteHost();
            HttpHost httpHost = getHttpHost(host);
            uri = StringUtils.cleanPath(
                    MULTIPLE_SLASH_PATTERN.matcher(host.getPath() + uri).replaceAll("/"));
            long contentLength = getContentLength(request);
    
            ContentType contentType = null;
    
            if (request.getContentType() != null) {
                contentType = ContentType.parse(request.getContentType());
            }
    
            InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength,
                    contentType);
    
            HttpRequest httpRequest = buildHttpRequest(verb, uri, entity, headers, params,
                    request);
            try {
                log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " "
                        + httpHost.getSchemeName());
                CloseableHttpResponse zuulResponse = forwardRequest(httpclient, httpHost,
                        httpRequest);
                this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(),
                        revertHeaders(zuulResponse.getAllHeaders()));
                return zuulResponse;
            }
            finally {
                // When HttpClient instance is no longer needed,
                // shut down the connection manager to ensure
                // immediate deallocation of all system resources
                // httpclient.getConnectionManager().shutdown();
            }
        }
    
        protected HttpRequest buildHttpRequest(String verb, String uri,
                InputStreamEntity entity, MultiValueMap<String, String> headers,
                MultiValueMap<String, String> params, HttpServletRequest request) {
            HttpRequest httpRequest;
            String uriWithQueryString = uri + (this.forceOriginalQueryStringEncoding
                    ? getEncodedQueryString(request) : this.helper.getQueryString(params));
    
            switch (verb.toUpperCase()) {
            case "POST":
                HttpPost httpPost = new HttpPost(uriWithQueryString);
                httpRequest = httpPost;
                httpPost.setEntity(entity);
                break;
            case "PUT":
                HttpPut httpPut = new HttpPut(uriWithQueryString);
                httpRequest = httpPut;
                httpPut.setEntity(entity);
                break;
            case "PATCH":
                HttpPatch httpPatch = new HttpPatch(uriWithQueryString);
                httpRequest = httpPatch;
                httpPatch.setEntity(entity);
                break;
            case "DELETE":
                BasicHttpEntityEnclosingRequest entityRequest = new BasicHttpEntityEnclosingRequest(
                        verb, uriWithQueryString);
                httpRequest = entityRequest;
                entityRequest.setEntity(entity);
                break;
            default:
                httpRequest = new BasicHttpRequest(verb, uriWithQueryString);
                log.debug(uriWithQueryString);
            }
    
            httpRequest.setHeaders(convertHeaders(headers));
            return httpRequest;
        }
    
        private String getEncodedQueryString(HttpServletRequest request) {
            String query = request.getQueryString();
            return (query != null) ? "?" + query : "";
        }
    
        private MultiValueMap<String, String> revertHeaders(Header[] headers) {
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            for (Header header : headers) {
                String name = header.getName();
                if (!map.containsKey(name)) {
                    map.put(name, new ArrayList<String>());
                }
                map.get(name).add(header.getValue());
            }
            return map;
        }
    
        private Header[] convertHeaders(MultiValueMap<String, String> headers) {
            List<Header> list = new ArrayList<>();
            for (String name : headers.keySet()) {
                for (String value : headers.get(name)) {
                    list.add(new BasicHeader(name, value));
                }
            }
            return list.toArray(new BasicHeader[0]);
        }
    
        private CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient,
                HttpHost httpHost, HttpRequest httpRequest) throws IOException {
            return httpclient.execute(httpHost, httpRequest);
        }
    
        private HttpHost getHttpHost(URL host) {
            HttpHost httpHost = new HttpHost(host.getHost(), host.getPort(),
                    host.getProtocol());
            return httpHost;
        }
    
        protected InputStream getRequestBody(HttpServletRequest request) {
            InputStream requestEntity = null;
            try {
                requestEntity = (InputStream) RequestContext.getCurrentContext()
                        .get(REQUEST_ENTITY_KEY);
                if (requestEntity == null) {
                    requestEntity = request.getInputStream();
                }
            }
            catch (IOException ex) {
                log.error("error during getRequestBody", ex);
            }
            return requestEntity;
        }
    
        private String getVerb(HttpServletRequest request) {
            String sMethod = request.getMethod();
            return sMethod.toUpperCase();
        }
    
        private void setResponse(HttpResponse response) throws IOException {
            RequestContext.getCurrentContext().set("zuulResponse", response);
            this.helper.setResponse(response.getStatusLine().getStatusCode(),
                    response.getEntity() == null ? null : response.getEntity().getContent(),
                    revertHeaders(response.getAllHeaders()));
        }
    
        /**
         * Add header names to exclude from proxied response in the current request.
         * @param names names of headers to exclude
         */
        protected void addIgnoredHeaders(String... names) {
            this.helper.addIgnoredHeaders(names);
        }
    
        /**
         * Determines whether the filter enables the validation for ssl hostnames.
         * @return true if enabled
         */
        boolean isSslHostnameValidationEnabled() {
            return this.sslHostnameValidationEnabled;
        }
    
        // Get the header value as a long in order to more correctly proxy very large requests
        protected long getContentLength(HttpServletRequest request) {
            if (useServlet31) {
                return request.getContentLengthLong();
            }
            String contentLengthHeader = request.getHeader(HttpHeaders.CONTENT_LENGTH);
            if (contentLengthHeader != null) {
                try {
                    return Long.parseLong(contentLengthHeader);
                }
                catch (NumberFormatException e) {
                }
            }
            return request.getContentLength();
        }
    
    }
    View Code

        其创建如下: org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration#simpleHostRoutingFilter2

        @Bean
        @ConditionalOnMissingBean({ SimpleHostRoutingFilter.class })
        public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
                ZuulProperties zuulProperties, CloseableHttpClient httpClient) {
            return new SimpleHostRoutingFilter(helper, zuulProperties, httpClient);
        }
    View Code

      可以看出其构造方法直接指定了httpClient 客户端,也就是直接使用httpCLient 进行转发请求。

    3. org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter

      这个后台路由转发的一些处理。也就是以forward 或者什么路径设置的路由。

    public class SendForwardFilter extends ZuulFilter {
    
        protected static final String SEND_FORWARD_FILTER_RAN = "sendForwardFilter.ran";
    
        @Override
        public String filterType() {
            return ROUTE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return SEND_FORWARD_FILTER_ORDER;
        }
    
        @Override
        public boolean shouldFilter() {
            RequestContext ctx = RequestContext.getCurrentContext();
            return ctx.containsKey(FORWARD_TO_KEY)
                    && !ctx.getBoolean(SEND_FORWARD_FILTER_RAN, false);
        }
    
        @Override
        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;
        }
    
    }
    View Code

    3. postFilter

    1. cn.qz.cloud.filter.PostFilter

        这个是我们自己的测试的后置过滤器。 到这一步 RequestContext 信息如下:(可以看到包含的一些信息,包括request 等信息)

     2. org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter

      如果没错误信息且有响应数据就走该过滤器,run 方法增加一些响应头、然后通过writeResponse 通过response.outputStream 进行写入数据。

    public class SendResponseFilter extends ZuulFilter {
    
        private static final Log log = LogFactory.getLog(SendResponseFilter.class);
    
        private boolean useServlet31 = true;
    
        private ZuulProperties zuulProperties;
    
        private ThreadLocal<byte[]> buffers;
    
        @Deprecated
        public SendResponseFilter() {
            this(new ZuulProperties());
        }
    
        public SendResponseFilter(ZuulProperties zuulProperties) {
            this.zuulProperties = zuulProperties;
            // To support Servlet API 3.1 we need to check if setContentLengthLong exists
            // minimum support in Spring 5 is 3.0 so we need to keep tihs
            try {
                HttpServletResponse.class.getMethod("setContentLengthLong", long.class);
            }
            catch (NoSuchMethodException e) {
                useServlet31 = false;
            }
            buffers = ThreadLocal
                    .withInitial(() -> new byte[zuulProperties.getInitialStreamBufferSize()]);
        }
    
        /* for testing */ boolean isUseServlet31() {
            return useServlet31;
        }
    
        @Override
        public String filterType() {
            return POST_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return SEND_RESPONSE_FILTER_ORDER;
        }
    
        @Override
        public boolean shouldFilter() {
            RequestContext context = RequestContext.getCurrentContext();
            return context.getThrowable() == null
                    && (!context.getZuulResponseHeaders().isEmpty()
                            || context.getResponseDataStream() != null
                            || context.getResponseBody() != null);
        }
    
        @Override
        public Object run() {
            try {
                addResponseHeaders();
                writeResponse();
            }
            catch (Exception ex) {
                ReflectionUtils.rethrowRuntimeException(ex);
            }
            return null;
        }
    
        private void writeResponse() throws Exception {
            RequestContext context = RequestContext.getCurrentContext();
            // there is no body to send
            if (context.getResponseBody() == null
                    && context.getResponseDataStream() == null) {
                return;
            }
            HttpServletResponse servletResponse = context.getResponse();
            if (servletResponse.getCharacterEncoding() == null) { // only set if not set
                servletResponse.setCharacterEncoding("UTF-8");
            }
    
            String servletResponseContentEncoding = getResponseContentEncoding(context);
            OutputStream outStream = servletResponse.getOutputStream();
            InputStream is = null;
            try {
                if (context.getResponseBody() != null) {
                    String body = context.getResponseBody();
                    is = new ByteArrayInputStream(
                            body.getBytes(servletResponse.getCharacterEncoding()));
                }
                else {
                    is = context.getResponseDataStream();
                    if (is != null && context.getResponseGZipped()) {
                        // if origin response is gzipped, and client has not requested gzip,
                        // decompress stream before sending to client
                        // else, stream gzip directly to client
                        if (isGzipRequested(context)) {
                            servletResponseContentEncoding = "gzip";
                        }
                        else {
                            servletResponseContentEncoding = null;
                            is = handleGzipStream(is);
                        }
                    }
                }
                if (servletResponseContentEncoding != null) {
                    servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING,
                            servletResponseContentEncoding);
                }
    
                if (is != null) {
                    writeResponse(is, outStream);
                }
            }
            finally {
                /**
                 * We must ensure that the InputStream provided by our upstream pooling
                 * mechanism is ALWAYS closed even in the case of wrapped streams, which are
                 * supplied by pooled sources such as Apache's
                 * PoolingHttpClientConnectionManager. In that particular case, the underlying
                 * HTTP connection will be returned back to the connection pool iif either
                 * close() is explicitly called, a read error occurs, or the end of the
                 * underlying stream is reached. If, however a write error occurs, we will end
                 * up leaking a connection from the pool without an explicit close()
                 *
                 * @author Johannes Edmeier
                 */
                if (is != null) {
                    try {
                        is.close();
                    }
                    catch (Exception ex) {
                        log.warn("Error while closing upstream input stream", ex);
                    }
                }
    
                // cleanup ThreadLocal when we are all done
                if (buffers != null) {
                    buffers.remove();
                }
    
                try {
                    Object zuulResponse = context.get("zuulResponse");
                    if (zuulResponse instanceof Closeable) {
                        ((Closeable) zuulResponse).close();
                    }
                    outStream.flush();
                    // The container will close the stream for us
                }
                catch (IOException ex) {
                    log.warn("Error while sending response to client: " + ex.getMessage());
                }
            }
        }
    
        protected InputStream handleGzipStream(InputStream in) throws Exception {
            // Record bytes read during GZip initialization to allow to rewind the stream if
            // needed
            //
            RecordingInputStream stream = new RecordingInputStream(in);
            try {
                return new GZIPInputStream(stream);
            }
            catch (java.util.zip.ZipException | java.io.EOFException ex) {
    
                if (stream.getBytesRead() == 0) {
                    // stream was empty, return the original "empty" stream
                    return in;
                }
                else {
                    // reset the stream and assume an unencoded response
                    log.warn(
                            "gzip response expected but failed to read gzip headers, assuming unencoded response for request "
                                    + RequestContext.getCurrentContext().getRequest()
                                            .getRequestURL().toString());
    
                    stream.reset();
                    return stream;
                }
            }
            finally {
                stream.stopRecording();
            }
        }
    
        protected boolean isGzipRequested(RequestContext context) {
            final String requestEncoding = context.getRequest()
                    .getHeader(ZuulHeaders.ACCEPT_ENCODING);
    
            return requestEncoding != null
                    && HTTPRequestUtils.getInstance().isGzipped(requestEncoding);
        }
    
        private String getResponseContentEncoding(RequestContext context) {
            List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
            if (zuulResponseHeaders != null) {
                for (Pair<String, String> it : zuulResponseHeaders) {
                    if (ZuulHeaders.CONTENT_ENCODING.equalsIgnoreCase(it.first())) {
                        return it.second();
                    }
                }
            }
            return null;
        }
    
        private void writeResponse(InputStream zin, OutputStream out) throws Exception {
            byte[] bytes = buffers.get();
            int bytesRead = -1;
            while ((bytesRead = zin.read(bytes)) != -1) {
                out.write(bytes, 0, bytesRead);
            }
        }
    
        private void addResponseHeaders() {
            RequestContext context = RequestContext.getCurrentContext();
            HttpServletResponse servletResponse = context.getResponse();
            if (this.zuulProperties.isIncludeDebugHeader()) {
                @SuppressWarnings("unchecked")
                List<String> rd = (List<String>) context.get(ROUTING_DEBUG_KEY);
                if (rd != null) {
                    StringBuilder debugHeader = new StringBuilder();
                    for (String it : rd) {
                        debugHeader.append("[[[" + it + "]]]");
                    }
                    servletResponse.addHeader(X_ZUUL_DEBUG_HEADER, debugHeader.toString());
                }
            }
            List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
            if (zuulResponseHeaders != null) {
                for (Pair<String, String> it : zuulResponseHeaders) {
                    if (!ZuulHeaders.CONTENT_ENCODING.equalsIgnoreCase(it.first())) {
                        servletResponse.addHeader(it.first(), it.second());
                    }
                }
            }
            if (includeContentLengthHeader(context)) {
                Long contentLength = context.getOriginContentLength();
                if (useServlet31) {
                    servletResponse.setContentLengthLong(contentLength);
                }
                else {
                    // Try and set some kind of content length if we can safely convert the
                    // Long to an int
                    if (isLongSafe(contentLength)) {
                        servletResponse.setContentLength(contentLength.intValue());
                    }
                }
            }
        }
    
        private boolean isLongSafe(long value) {
            return value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE;
        }
    
        protected boolean includeContentLengthHeader(RequestContext context) {
            // Not configured to forward the header
            if (!this.zuulProperties.isSetContentLength()) {
                return false;
            }
    
            // Only if Content-Length is provided
            if (context.getOriginContentLength() == null) {
                return false;
            }
    
            // If response is compressed, include header only if we are not about to
            // decompress it
            if (context.getResponseGZipped()) {
                return context.isGzipRequested();
            }
    
            // Forward it in all other cases
            return true;
        }
    
        /**
         * InputStream recording bytes read to allow for a reset() until recording is stopped.
         */
        private static class RecordingInputStream extends InputStream {
    
            private InputStream delegate;
    
            private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    
            RecordingInputStream(InputStream delegate) {
                super();
                this.delegate = Objects.requireNonNull(delegate);
            }
    
            @Override
            public int read() throws IOException {
                int read = delegate.read();
    
                if (buffer != null && read != -1) {
                    buffer.write(read);
                }
    
                return read;
            }
    
            @Override
            public int read(byte[] b, int off, int len) throws IOException {
                int read = delegate.read(b, off, len);
    
                if (buffer != null && read != -1) {
                    buffer.write(b, off, read);
                }
    
                return read;
            }
    
            public void reset() {
                if (buffer == null) {
                    throw new IllegalStateException("Stream is not recording");
                }
    
                this.delegate = new SequenceInputStream(
                        new ByteArrayInputStream(buffer.toByteArray()), delegate);
                this.buffer = new ByteArrayOutputStream();
            }
    
            public int getBytesRead() {
                return (buffer == null) ? -1 : buffer.size();
            }
    
            public void stopRecording() {
                this.buffer = null;
            }
    
            @Override
            public void close() throws IOException {
                this.delegate.close();
            }
    
        }
    
    }
    View Code

    3. /cloud-zuul/api/v1/payment/pay/getServerPort 路由原理

      这种配置方式不经过SpringMVC 路由,从tomcat 直接路由到后端对应的ZuulServlet。查看其调用链如下:

     

       可以看出其调用链没经过SpringMVC的DispatcherServlet。

      这种方式经过的Filter和上面的一样,只是这里不经过DispatcherServlet, 所以ServletDetectionFilter 放置的IS_DISPATCHER_SERVLET_REQUEST_KEY 为false。 后面Servlet30WrapperFilter 也不会对Request 进行包装。

      到cn.qz.cloud.filter.PostFilter 的RequestContext 信息如下:

     

    4. 文件上传

      下面研究文件上传在两种模式下的区别。

      过SpringMVC和不过MVC的两种方式都可以上传文件。官方建议是大文件上传绕过SpringMVC,直接走第二种ZuulServlet 的方式(/cloud-zuul/** 路径方式),因为过SpringMVC的话会占用内存,相当于会把文件解析一下。

    两者区别:

    1. 过SpringMVC:

    (1) 如果是文件上传请求,会包装request 对象并提取相关参数:在 org.springframework.web.servlet.DispatcherServlet#doDispatch 方法中会调用checkMultipart 方法,然后监测如果是文件上传的请求(根据请求类型判断StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/")) 就将request 包装为 org.springframework.web.multipart.support.StandardMultipartHttpServletRequest(传到ZuulServlet.service 方法是request 是该类型), 并将相关的文件信息(相对于Tomcat的路径等信息)抽取到 org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#multipartParameterNames 属性中

     (2) org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter#run 前置过滤器会包装表单,这里会占用内存:解析文件(自己测试是在这里调用 解析占用内存), 调用wrapper.getContentType() 会调用org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter.FormBodyRequestWrapper#buildContentData 解析内容

    2. 不过SpringMVC

      这种不会对request 进行包装,也不会过FormBodyWrapperFilter,传到ZuulServlet.service 方法的request的实际类型是 org.apache.catalina.connector.RequestFacade。 这种也不会占用内存,对于大文件上传可以采用这种方式。

     

    补充:SpringMVC 也可以关掉文件上传解析模块

      如果是文件上传,文件会在SpringMVC的中央处理器入口就提取文件参数,并转换Request。(源码位置: org.springframework.web.servlet.DispatcherServlet#checkMultipart)。 也可以关掉SpringMVC的文件上传(关掉之后文件解析的Resolver 为null,就不会解析文件),yml 配置spring.servlet.multipart.enabled=false。 参考自动配置类org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration。

    参考: https://www.springcloud.cc/spring-cloud-netflix.html

  • 相关阅读:
    TIM时钟频率计算
    时钟节拍tick
    Continue作用
    struct结构体的字节长度,字节对齐
    IAR所包含的头文件位置
    Oracle存储过程给变量赋值的方法
    DataTable如何去除重复的行
    C#遍历窗体所有控件或某类型所有控件
    SqlServer无备份下误删数据恢复
    45.4.7 序列:USER_SEQUENCES(SEQ)
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/16132502.html
Copyright © 2020-2023  润新知