Spring Cloud Microservices Beginner Tutorial (8): Spring Cloud Zuul API Gateway Dynamic Routing, Cookie Passing, and CORS

In the previous section we covered asynchronous communication between microservices via message queues. This section introduces the API gateway in microservices, Spring Cloud Zuul, which can centrally manage numerous interfaces and provide load balancing, among other functions.

Spring Cloud Microservices Beginner Tutorial (1): Microservices Introduction Spring Cloud Microservices Beginner Tutorial (2): Service Registration and Discovery — Eureka Spring Cloud Microservices Beginner Tutorial (3): Service Registration Spring Cloud Microservices Beginner Tutorial (4): Inter-service Invocation — FeignClient Spring Cloud Microservices Beginner Tutorial (5): Centralized Config — ConfigService Spring Cloud Microservices Beginner Tutorial (6): Spring Cloud BUS Message Bus for Dynamic Config Refresh Spring Cloud Microservices Beginner Tutorial (7): Spring Cloud Stream Message-driven Microservices Spring Cloud Microservices Beginner Tutorial (8): Spring Cloud Zuul API Gateway Dynamic Routing, Cookie Passing, and CORS Spring Cloud Microservices Beginner Tutorial (9): Zuul Gateway Integrating Swagger2 for Auto-generated RESTful API Docs Spring Cloud Microservices Beginner Tutorial (10): Spring Cloud Hystrix Circuit Breaking and Service Degradation Spring Cloud Microservices Beginner Tutorial (11): Spring Cloud Sleuth + Zipkin Service Tracing and Link Monitoring Spring Cloud Microservices Beginner Tutorial (12): Spring Cloud Docker Containerized Deployment

Code will be shared at https://github.com/NeilRen/SpringCloudDemo, with different branches for different chapters; the Master branch contains the final combined content. This chapter’s code is at: https://github.com/NeilRen/SpringCloudDemo/tree/feature/spring-cloud-zuul

Original This article is original, author: Ren Fei. Please cite the author and source when reposting.

In the previous section we covered asynchronous communication between microservices via message queues. This section introduces the API gateway in microservices, Spring Cloud Zuul, which can centrally manage numerous interfaces and provide load balancing, among other functions.

Create a New Gateway Module

Create a new Maven gateway module with the dependency spring-cloud-starter-netflix-zuul. Modify the 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>net.renfei</groupId>
	        <version>1.0.0</version>
	    </parent>
	    <modelVersion>4.0.0</modelVersion>
	    <groupId>net.renfei</groupId>
	    <artifactId>gateway</artifactId>
	    <version>1.0.0</version>

	    <dependencies>
	        <dependency>
	            <groupId>org.springframework.cloud</groupId>
	            <artifactId>spring-cloud-starter-config</artifactId>
	        </dependency>
	        <dependency>
	            <groupId>org.springframework.cloud</groupId>
	            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
	        </dependency>
	        <dependency>
	            <groupId>org.springframework.cloud</groupId>
	            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
	        </dependency>
	    </dependencies>
	</project>

Add an application startup class as the entry point, with the @EnableZuulProxy annotation:

package net.renfei.gateway;

	import org.springframework.boot.SpringApplication;
	import org.springframework.boot.autoconfigure.SpringBootApplication;
	import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
	import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

	@EnableZuulProxy
	@EnableEurekaClient
	@SpringBootApplication
	public class GatewayApplication {
	    public static void main(String[] args) {
	        SpringApplication.run(GatewayApplication.class, args);
	    }
	}

And the configuration file bootstrap.yml, which can dynamically fetch config. For convenience of demonstration, I write the config directly into bootstrap.yml; in reality it would be in the Git remote config center:

spring:
	  application:
	    name: Gateway
	  cloud:
	    config:
	      discovery:
	        service-id: config
	        enabled: true
	      profile: dev
	eureka:
	  client:
	    service-url:
	      defaultZone: http://localhost:8761/eureka/
	server:
	  port: 8080
	zuul:
	  routes:
	    democlientroute:
	      path: /DC/**
	      service-id: DemoClient
	      sensitiveHeaders:
	    demoserviceroute:
	      path: /DS/**
	      service-id: DemoService
	  ignored-patterns:
	    - /admin
	    - /myadmin

The name after zuul.routes is freely definable. path is the request path pattern to match; service-id is the service name the route maps to. The role of sensitiveHeaders is to let Zuul forward request headers, including Cookie, to the backend service. This config defaults to org.springframework.cloud.netflix.zuul.filters.ZuulProperties#sensitiveHeaders, where the default is private Set<String> sensitiveHeaders = new LinkedHashSet(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));. If your headers are not among these three, you can customize them in the config file. ignored-patterns means these addresses are ignored and not forwarded.

Zuul Filters

The core of the Zuul gateway is essentially a stack of filters, so you’ll write many filters here. Since there are quite a few, I’ll put them in a table:

TypeOrderFilterFunction
pre-3ServletDetectionFilterMark the type of Servlet handling the request
pre-2Servlet30WrapperFilterWrap the HttpServletRequest
pre-1FormBodyWrapperFilterWrap the request body
route1DebugFilterMark the debug flag
route5PreDecorationFilterProcess the request context for later use
route10RibbonRoutingFilterserviceId
route100SimpleHostRoutingFilterurl request forwarding
route500SendForwardFilterforward request forwarding
post0SendErrorFilterHandle responses with errors
post1000SendResponseFilterHandle normal responses

We extend com.netflix.zuul.ZuulFilter; let’s see what settings are available:

package net.renfei.gateway.filter;

	import com.netflix.zuul.ZuulFilter;
	import com.netflix.zuul.exception.ZuulException;
	import org.springframework.stereotype.Component;
	import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
	import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

	/**
	 * Authorization filter; can determine whether the user has access
	 *
	 * @author RenFei
	 */
	@Component
	public class AuthorizationFilter extends ZuulFilter {
	    @Override
	    public String filterType() {
	        return PRE_TYPE;
	    }
	    @Override
	    public int filterOrder() {
	        return PRE_DECORATION_FILTER_ORDER - 1;
	    }
	    @Override
	    public boolean shouldFilter() {
	        return true;
	    }
	    @Override
	    public Object run() throws ZuulException {
	        // Execute your judgment logic here
	        return null;
	    }
	}

Simple, right? First set a type, then set the execution order, and run() holds the logic you want to execute.

Dynamic Refresh of Zuul Routing

The gateway manages many service interfaces, and restarting every time you change config badly affects production. In the previous section we already built the Spring Cloud BUS message bus to enable dynamic config refresh, so we’ll use the BUS to refresh config. With the dependency spring-cloud-starter-config in the POM, create a config class and add the @RefreshScope annotation for dynamic refresh. Since I’m demonstrating, I won’t fuss with the Git config file; here is the reference code:

package net.renfei.gateway.config;

	import org.springframework.boot.context.properties.ConfigurationProperties;
	import org.springframework.cloud.context.config.annotation.RefreshScope;
	import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
	import org.springframework.stereotype.Component;

	/**
	 * Zuul config that dynamically pulls from the config center
	 *
	 * @author RenFei
	 */
	@Component
	public class ZuulConfig {
	    @RefreshScope
	    @ConfigurationProperties("zuul")
	    public ZuulProperties zuulProperties() {
	        return new ZuulProperties();
	    }
	}

Zuul Gateway CORS Configuration

We create a config class CorsConfig and register a Bean: CorsFilter, then configure it:

package net.renfei.gateway.config;

	import org.springframework.context.annotation.Bean;
	import org.springframework.stereotype.Component;
	import org.springframework.web.cors.CorsConfiguration;
	import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
	import org.springframework.web.filter.CorsFilter;
	import java.util.Arrays;

	/**
	 * CORS configuration
	 *
	 * @author RenFei
	 */
	@Component
	public class CorsConfig {
	    @Bean
	    public CorsFilter corsFilter() {
	        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
	        CorsConfiguration config = new CorsConfiguration();
	        // Whether to allow Cookie cross-origin
	        config.setAllowCredentials(true);
	        // List of allowed origins
	        config.setAllowedOrigins(Arrays.asList("*"));
	        // Allowed headers
	        config.setAllowedHeaders(Arrays.asList("*"));
	        // Allowed methods, GET, POST...
	        config.setAllowedMethods(Arrays.asList("*"));
	        // Allowed cross-origin duration; within this period cross-origin is not re-checked
	        config.setMaxAge(300L);
	        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", config);
	        return new CorsFilter(urlBasedCorsConfigurationSource);
	    }
	}

The code already has comments, so no more talk — a basic gateway is now set up.