Gateway processing flow of Zuul source code analysis

Gateway processing flow of Zuul source code analysis

Zuul processing flow

1. spring-cloud-starter-zuul starter

  • Let's first check what is in the spring-cloud-starter-zuul starter package. The focus here is the pom.xml file, ZuulDeprecationWarningAutoConfiguration.java

  • Open org.springframework.cloud/spring-cloud-starter-zuul/pom.xml, you can see that it depends on spring-cloud-starter-netflix-zuul

<?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" > <modelVersion> 4.0 .0 </modelVersion> <parent> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix</artifactId> <version> 2.2 .5 .RELEASE</version> <relativePath>..</relativePath> <!-- lookup parent from repository --> </parent> <artifactId>spring-cloud-starter-zuul</artifactId> <name>spring-cloud-starter-zuul</name> <description> Spring Cloud Starter Zuul (deprecated, please use spring-cloud-starter-netflix-zuul) </description> <url>https: //projects.spring.io/spring-cloud</url> <organization> <name>Pivotal Software, Inc.</name> <url>https: //www.spring.io</url> </organization> <properties> <main.basedir>$ {basedir}/../..</main.basedir> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies> </project> Copy code
  • Let's check the spring-cloud-starter-netflix-zuul package

  • Open pom.xml and you can see that it relies on com.netflix.zuul, so Spring Cloud Zuul is implemented based on netflix's zuul, in addition to adding hystrix and ribbon dependencies, so zuul comes with these two functions Yes, the spring-boot-starter-web dependency can make the application a web application, and the spring-boot-starter-actuator is a monitoring dependency

<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" > <modelVersion> 4.0 .0 </modelVersion> <parent> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix</artifactId> <version> 2.2 .5 .RELEASE</version> </parent> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> <name>Spring Cloud Starter Netflix Zuul</name> <description>Spring Cloud Starter Netflix Zuul</description> <url>https: //projects.spring.io/spring-cloud</url> <organization> <name>Pivotal Software, Inc.</name> <url>https: //www.spring.io</url> </organization> <properties> <main.basedir>${basedir}/../../..</main.basedir> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-archaius</artifactId> </dependency> <dependency> <groupId>com.netflix.zuul</groupId> <artifactId>zuul-core</artifactId> </dependency> </dependencies> </project> Copy code
  • /META-INF/spring.provides depends on spring-platform-netflix-core module and zuul-core module
    1 provides: spring-platform-netflix-core, zuul-core
  • Now we enter spring-platform-netflix-core to see how Spring integrates a series of Netflix frameworks. Below is the code framework diagram
  • You can see that this JAR package also contains the spring.factories file, so this configuration file will be retrieved when the SpringBoot project starts. This file is the key to zuul's automatic registration configuration. You can see the familiar zuul, hystrix, feign, ribbon below Auto-configuration class
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration,\ org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration,\ org.springframework.cloud.netflix.feign.FeignAutoConfiguration,\ org.springframework.cloud.netflix.feign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\ org.springframework.cloud.netflix.feign.encoding.FeignContentGzipEncodingAutoConfiguration,\ org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration,\ org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration,\ org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,\ org.springframework.cloud.netflix.rx.RxJavaAutoConfiguration,\ org.springframework.cloud.netflix.metrics.servo.ServoMetricsAutoConfiguration,\ org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\ org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\ org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.cloud.netflix.metrics.ServoEnvironmentPostProcessor Copy code
  • We are now concerned about Zuul s auto-configuration classes. From the spring.factories file above, we can see that there are two auto-configuration classes related to Zuul. The following figure shows that these two have an inheritance relationship. ZuulProxyAutoConfiguration has the most complete function.

  • ZuulServerAutoConfiguration and ZuulProxyAutoConfiguration

  • ZuulServerAutoConfiguration auto configuration class, if @EnableZuulServer is on the startup class, this class will take effect

1. In the code below, you can see that @Conditional is widely used as a conditional judgment. Pay attention to the ZuulController Bean, which is the request entry of Zuul. This class implements the Controller, indicating that Spring MVC DispatcherServlet is also used here. 2. At the same time, this class A large number of ZuulFilter 3 codes are registered:

/** * @author */ @Configuration //The declaration is the configuration class @EnableConfigurationProperties({ ZuulProperties.class }) //Activate zuul configuration @ConditionalOnClass(ZuulServlet.class) //Condition 1 exists ZuulServlet.class @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class ) //Condition 2 There is a ZuulServerMarkerConfiguration.Marker.class bean, that is, the application uses the @EnableZuulServer annotation //Make sure to get the ServerProperties from the same place as a normal web app would @Import(ServerPropertiesAutoConfiguration.class) //Configure the ServerProperties instance public class ZuulServerAutoConfiguration { @Autowired protected ZuulProperties zuulProperties; @Autowired protected ServerProperties server; @Autowired(required = false) private ErrorController errorController; @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 new SimpleRouteLocator ( the this .server.getServletPrefix (), the this .zuulProperties); } /** * zuulController, wraps a ZuulServlet type servlet, realizes the initialization of ZuulServlet type servlet. * * @return */ @Bean public ZuulController zuulController () { return new ZuulController(); } @Bean public ZuulHandlerMapping zuulHandlerMapping (RouteLocator routes) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController( this .errorController); return mapping; } @Bean public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener () { return new ZuulRefreshListener(); } @Bean @ConditionalOnMissingBean(name = "zuulServlet") public ServletRegistrationBean zuulServlet () { ServletRegistrationBean servlet = new ServletRegistrationBean( new ZuulServlet(), this .zuulProperties.getServletPattern()); //The whole point of exposing this servlet is to provide a route that doesn't //buffer requests. servlet.addInitParameter( "buffer- requests" , "false" ); return servlet; } //pre filters @Bean public ServletDetectionFilter servletDetectionFilter () { return new ServletDetectionFilter(); } @Bean public FormBodyWrapperFilter formBodyWrapperFilter () { return new FormBodyWrapperFilter(); } @Bean public DebugFilter debugFilter () { return new DebugFilter(); } @Bean public Servlet30WrapperFilter servlet30WrapperFilter () { return new Servlet30WrapperFilter(); } //post filters @Bean public SendResponseFilter sendResponseFilter () { return new SendResponseFilter(); } @Bean public SendErrorFilter sendErrorFilter () { return new SendErrorFilter(); } @Bean public SendForwardFilter sendForwardFilter () { return new SendForwardFilter(); } @Bean @ConditionalOnProperty(value = "zuul.ribbon.eager-load.enabled", matchIfMissing = false) public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer ( SpringClientFactory springClientFactory) { return new ZuulRouteApplicationContextInitializer(springClientFactory, zuulProperties); } @Configuration protected static class ZuulFilterConfiguration { @Autowired private Map<String, ZuulFilter> filters; @Bean public ZuulFilterInitializer zuulFilterInitializer ( CounterFactory counterFactory, TracerFactory tracerFactory) { FilterLoader filterLoader = FilterLoader.getInstance(); FilterRegistry filterRegistry = FilterRegistry.instance(); return new ZuulFilterInitializer( this .filters, counterFactory, tracerFactory, filterLoader, filterRegistry); } } @Configuration @ConditionalOnClass(CounterService.class) protected static class ZuulCounterFactoryConfiguration { @Bean @ConditionalOnBean(CounterService.class) public CounterFactory counterFactory (CounterService counterService) { return new DefaultCounterFactory(counterService); } } @Configuration protected static class ZuulMetricsConfiguration { @Bean @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) { this .zuulHandlerMapping.setDirty( true ); } else if (event instanceof HeartbeatEvent) { if ( this .heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) { this .zuulHandlerMapping.setDirty( true ); } } } } } Copy code
  • ZuulProxyAutoConfiguration automatic configuration class, if there is a corresponding @EnableZuulProxy on the startup class, this class will take effect
  • From the above inheritance diagram, we can find that this class inherits ZuulServerAutoConfiguration, so this class has all the functions of ZuulServerAutoConfiguration, and on this basis, the use of service discovery as a routing addressing function
  • Code:
/** * @author */ @Configuration //The declaration is the configuration class @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,//Introduce RibbonCommandFactory configuration RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class, RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class, HttpClientConfiguration.class }) @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class) //Condition 2 There is a ZuulProxyMarkerConfiguration.Marker.class bean, that is, the application uses @EnableZuulProxy annotation public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration { @SuppressWarnings("rawtypes") @Autowired(required = false) private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList(); /** * Gateway service registration instance information */ @Autowired(required = false) private Registration registration; /** * Service discovery client */ @Autowired private DiscoveryClient discovery; /** * The mapping logic of serviceId and routing */ @Autowired private ServiceRouteMapper serviceRouteMapper; @Override public HasFeatures zuulFeature () { return HasFeatures.namedFeature( "Zuul (Discovery)" , ZuulProxyAutoConfiguration.class); } /** * Static and dynamic routing addressing: Static is obtained from the configuration file, and the dynamic is completed by the service discovery client. The latter has a higher priority * @return */ @Bean @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class) public DiscoveryClientRouteLocator discoveryRouteLocator () { return new DiscoveryClientRouteLocator( this .server.getServletPrefix(), this .discovery, this .zuulProperties, this .serviceRouteMapper, this .registration); } //pre filters @Bean public PreDecorationFilter preDecorationFilter (RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) { return new PreDecorationFilter(routeLocator, this .server.getServletPrefix(), this .zuulProperties, proxyRequestHelper); } //route filters @Bean 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 public ApplicationListener<ApplicationEvent> zuulDiscoveryRefreshRoutesListener () { return new ZuulDiscoveryRefreshListener(); } @Bean @ConditionalOnMissingBean(ServiceRouteMapper.class) public ServiceRouteMapper serviceRouteMapper () { return new SimpleServiceRouteMapper(); } @Configuration @ConditionalOnMissingClass("org.springframework.boot.actuate.endpoint.Endpoint") protected static class NoActuatorConfiguration { @Bean public ProxyRequestHelper proxyRequestHelper (ZuulProperties zuulProperties) { ProxyRequestHelper helper = new ProxyRequestHelper(); helper.setIgnoredHeaders(zuulProperties.getIgnoredHeaders()); helper.setTraceRequestBody(zuulProperties.isTraceRequestBody()); return helper; } } /** * Add Endpoint */ @Configuration @ConditionalOnClass(Endpoint.class) protected static class EndpointConfiguration { @Autowired(required = false) private TraceRepository traces; @ConditionalOnEnabledEndpoint("routes") @Bean public RoutesEndpoint routesEndpoint (RouteLocator routeLocator) { return new RoutesEndpoint(routeLocator); } @ConditionalOnEnabledEndpoint("routes") @Bean public RoutesMvcEndpoint routesMvcEndpoint (RouteLocator routeLocator, RoutesEndpoint endpoint) { return new RoutesMvcEndpoint(endpoint, routeLocator); } @ConditionalOnEnabledEndpoint("filters") @Bean public FiltersEndpoint filtersEndpoint () { FilterRegistry filterRegistry = FilterRegistry.instance(); return new FiltersEndpoint(filterRegistry); } @Bean public ProxyRequestHelper proxyRequestHelper (ZuulProperties zuulProperties) { TraceProxyRequestHelper helper = new TraceProxyRequestHelper(); if ( this .traces != null ) { helper.setTraces( this .traces); } helper.setIgnoredHeaders(zuulProperties.getIgnoredHeaders()); helper.setTraceRequestBody(zuulProperties.isTraceRequestBody()); return helper; } } private static class ZuulDiscoveryRefreshListener implements ApplicationListener < ApplicationEvent > { private HeartbeatMonitor monitor = new HeartbeatMonitor(); @Autowired private ZuulHandlerMapping zuulHandlerMapping; @Override public void onApplicationEvent (ApplicationEvent event) { if (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 .monitor.update(value)) { reset(); } } private void reset () { this .zuulHandlerMapping.setDirty( true ); } } } Copy code
  • Which mode ZuulServerAutoConfiguration and ZuulProxyAutoConfiguration use are distinguished by @EnableZuulServer and @EnableZuulProxy annotations respectively
  • The former uses ZuulProperties to configure routing addressing;
  • The latter adds the use of service discovery as a routing addressing function on the original basis, and uses Ribbon for client load balancing, which is the most commonly used;

2. @EnableZuulProxy

  • @EnableZuulProxy annotation
/** * Sets up a Zuul server endpoint and installs some reverse proxy filters in it, so it can * forward requests to backend servers. The backends can be registered manually through * configuration or via DiscoveryClient. * * @see EnableZuulServer for how to get a Zuul server without any proxying * * @author */ @EnableCircuitBreaker @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(ZuulProxyMarkerConfiguration.class) public @interface EnableZuulProxy { } Copy code
  • @EnableZuulProxy analysis
  • @EnableCircuitBreaker annotation is used to enable the short circuit breaker function
/** * Annotation to enable a CircuitBreaker implementation. * http://martinfowler.com/bliki/CircuitBreaker.html * @author */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(EnableCircuitBreakerImportSelector.class) public @interface EnableCircuitBreaker { } Copy code
  • @Import(ZuulProxyMarkerConfiguration.class)
  • ZuulProxyMarkerConfiguration.Marker.class
@Configuration public class ZuulProxyMarkerConfiguration { @Bean public Marker zuulProxyMarkerBean () { return new Marker(); } class Marker { } } Copy code

3. use Consul as the registration center

  • Zuul in @EnableZuulProxy mode needs the support of the registry, because eureka has been abandoned, I chose Consul here

1. Add Maven dependency

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> Copy code

2. Add @EnableDiscoveryClient annotation to the startup class

@EnableZuulProxy @EnableDiscoveryClient @SpringBootApplication public class ZuulApplication { public static void main (String[] args) { SpringApplication.run(ZuulApplication.class, args); } } Copy code

3. After passing all the above steps, the entire service gateway Zuul application can function

4. summary

1. Spring Cloud has encapsulated and integrated Netflix Zuul, which makes it more convenient to use Zuul in the Spring Cloud environment. You only need to add spring-cloud-starter-zuul maven dependency and add @EnableZuulProxy to the startup class to create a zuul application. 2. Spring Cloud Zuul actually adds some ZuulFilter to the Servlet to complete some additional things, and encapsulate it into a framework.