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:
| Type | Order | Filter | Function |
|---|---|---|---|
| pre | -3 | ServletDetectionFilter | Mark the type of Servlet handling the request |
| pre | -2 | Servlet30WrapperFilter | Wrap the HttpServletRequest |
| pre | -1 | FormBodyWrapperFilter | Wrap the request body |
| route | 1 | DebugFilter | Mark the debug flag |
| route | 5 | PreDecorationFilter | Process the request context for later use |
| route | 10 | RibbonRoutingFilter | serviceId |
| route | 100 | SimpleHostRoutingFilter | url request forwarding |
| route | 500 | SendForwardFilter | forward request forwarding |
| post | 0 | SendErrorFilter | Handle responses with errors |
| post | 1000 | SendResponseFilter | Handle 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.
