Getting Started with Spring Cloud Gateway — Part One
The post focuses on a brief introduction to the Spring cloud gateway, and some of its practical use cases with example implementation.
What is Spring Cloud Gateway?
Spring Cloud Gateway is a powerful solution provided by the Spring ecosystem. It is built as a Spring Boot application that runs on Netty and utilizes a reactive programming approach. Before delving into Spring Cloud Gateway (SCG) and its use case, let’s briefly understand what it is and its role in a microservice environment.
What is a Gateway?
A gateway in a microservice architecture acts as a single point of entry between clients and a cluster of microservices. It plays a vital role in cross-cutting concerns by handling tasks such as authentication, rate limiting, common business validation, and more.
Why use Spring Cloud Gateway?
Spring Cloud Gateway simplifies the process of constructing and deploying a gateway. Users can quickly set up and run their gateway with a set of configuration files. Additionally, SCG offers numerous customization options, allowing developers to tailor the gateway to their specific requirements.
How does it work?
The architecture of the Spring cloud gateway looks like below.
Here are the key components of the Spring Cloud gateway architecture:
- Routes: Spring Cloud Gateway allows you to define a set of rules called routes that determine how incoming requests should be handled. Routes are configured using a combination of predicates and filters. Predicates are conditions based on request attributes (e.g., path, headers) that determine if a route should be applied. Filters, on the other hand, manipulate the request and response as they pass through the gateway.
- Predicates: When an incoming request arrives, the Gateway handler matches the request against the defined routes using predicates. If a match is found, the request is forwarded to the appropriate destination, which can be a specific microservice, a load balancer, or any other backend service.
- Filters: Filters allow you to modify the request and response as it flows through the gateway. Filters can be used for various purposes such as authentication, authorization, rate limiting, logging, request/response transformation, and more. Spring Cloud Gateway provides a rich set of built-in filters and also allows you to create custom filters to cater to specific requirements. These filters can be one URL per se or a global filter.
Setup and Implementation
The setup involves adding the org.springframework.cloud:spring-cloud-starter-gateway dependency or including the spring cloud routing dependency if the project is created from the spring initializer. Remember the spring web dependency is not required here as netty is used.
The SGC application will look for the RouteLocator
bean to retrieve the app route configuration. SGC provide two ways to do it. Either as DSL through the yaml configuration file or the Java code. The post will cover both ways for each scenario.
1. Routing:
Let's look at basic routing. Given that the user detail service is behind the gateway, we want to expose the user detail service endpoints. For example, the route http://localhost:8080/api/users has to be routed to the user list endpoint https://reqres.in/api/users.
This can be achieved by declaring the below bean.
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_route", r -> r.path("/**")
.uri("https://reqres.in/"))
.build();
}
or by adding the below yaml to the application.yaml file
spring:
cloud:
gateway:
routes:
- id: user_route
uri: https://reqres.in
predicates:
- Path=/**
As discussed earlier each route defined will take a predicate and an optional filter. In the above code, the predicate takes a parameter with two values. The name of the predicate path
matches the incoming request path with the regex pattern /**
provided as the second argument. The above wildcard regex matched every request to the user_route
mapping and routed the request to https://reqres.in
However, in the usual case, several microservices will be behind the gateway. Assume the path that starts with /user-service
should be routed to the https://reqres.in
this can be achieved as below.
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_route", p -> p.path("/user-service/**")
.filters(f -> f.rewritePath("/user-service/(?<suburl>.*)", "/api/${suburl}"))
.uri("https://reqres.in/"))
.build();
}
spring:
cloud:
gateway:
routes:
- id: user_route
uri: https://reqres.in
predicates:
- Path=/user-service/**
filters:
- RewritePath=/user-service/(?<suburl>.*),/api/${suburl}
Here only the requests whose paths match /user-service/**
will be routed to https://reqres.in.
Also, we use a filter named ReWritePath
which rewrites the incoming request URL. So any value behind /user-service
will be rewritten as /api/${subrl}
. That is the request to http://localhost:8080/user-service/users is routed to https://reqres.in/api/users. Not only is the path, but it also carries the query parameters present in the incoming request to the destination URL.
Before moving on next use case, the point to note here is that the path
predicate and ReWritePath
filter factory used above is one of the in-build predicates and filters offered by SCG. There are many more route predicates provided by Spring to match different attributes such as Header, Cookies, Method, Host, Path etc
2. Modifying requests and responses with filters:
Filters can modify both requests and responses. Similar to ReWritePath
filter factory which modifies the request path as seen in the earlier use case, Spring provides many inbuilt filters such as AddRequestParam
AddResponseHeader
ReWriteLocationResponse
etc.
In this example, let's say the response from the subsystem has a header that we don't want to send back to the user, then that can be removed using the removeResponseHeader
filter as below.
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_route", p -> p.path("/user-service/**")
.filters(f -> f.rewritePath("/user-service/(?<suburl>.*)", "/api/${suburl}")
.removeResponseHeader("X-Powered-By"))
.uri("https://reqres.in/"))
.build();
}
spring:
cloud:
gateway:
routes:
- id: user_route
uri: https://reqres.in
predicates:
- Path=/user-service/**
filters:
- RewritePath=/user-service/(?<suburl>.*),/api/${suburl}
- RemoveResponseHeader=X-Powered-By
3. Circuit Breaking:
Circuit breaking in a microservice is a strategy in which if one or more downstream services are failing or responding late, then the circuit is opened after a certain threshold limit. After a certain time, the circuit will be half-opened allowing a limited number of requests and the circuit is closed when the downstream service is confirmed to be recovered. In this way, we prevent cascading failure that might result in unwanted resource consumption.
To add circuit breaking, the Spring cloud gateway looks for the implementation of SpringCloudCircuitBreakerFilterFactory. Spring cloud gateway supports implementations like resilence4j, Hystrix, and Spring retry.
In the below example, we use resilience4j. To do that add the below dependencies.
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
On finding the SpringCloudCircuitBreakerResilience4JFilterFactory in the classpath the spring will autoconfigure the circuit breaker for us.
Then the circuit breaker can be added to the route as below.
- id: user_route
uri: https://reqres.in
predicates:
- Path=/user-service/**
filters:
- RewritePath=/user-service/(?<suburl>.*),/api/${suburl}
- RemoveResponseHeader=X-Powered-By
- CircuitBreaker=myCircuitBreaker
And the Java equivalent for the above is.
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_route", p -> p.path("/user-service/**")
.filters(f -> f.rewritePath("/user-service/(?<suburl>.*)", "/api/${suburl}")
.removeResponseHeader("X-Powered-By")
.circuitBreaker(c -> c.setName("appCircuitBreaker")))
.uri("https://reqresss.in/"))
.build();
}
Here we are adding a circuit breaker with default configuration to the user list endpoint and giving it the name `appCircuitBreaker`.
However, the default configurations can be overridden by defining the properties specific to the chosen circuit breaker implementation. In the case of resilience4j, the customisation looks like below.
resilience4j:
circuitbreaker:
instances:
appCircuitBreaker:
slidingWindowSize: 100
failureRateThreshold: 50
minimumNumberOfCalls: 10
waitDurationInOpenState: 10s
timelimiter:
instances:
appCircuitBreaker:
timeoutDuration: 5s
In the above code, the circuit breaker monitors consider the last 20 requests. If the failure rate exceeds 50% over the last 10 requests, the circuit breaker trips. Then, it enters a half-open state after 10 seconds, allowing a few requests to test the service’s recovery before potentially tripping again.
The Java equivalent for the above configuration is to define a bean as below.
public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.slidingWindowSize(20)
.minimumNumberOfCalls(10)
.waitDurationInOpenState(Duration.ofSeconds(10))
.build())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(5))
.build())
.build());
}
Below is the repo for the above use case that is tried.
https://github.com/Jayarmananan1994/gatewaydemo
Thus Spring Cloud Gateway simplifies building API Gateways in the microservices architecture. However, it’s essential to acknowledge potential drawbacks. For example, Since Spring Cloud Gateway serves as the single entry point, its failure can render all downstream services inaccessible. Overwhelming traffic or configuration errors can cause this. To some extent, this can be mitigated by scaling the SGS instance horizontally. In the subsequent post, a few other use cases such as rate limiting, and authentication can be discussed utilising the custom gateway filter. Please do comment on your thoughts and feedback. Let’s foster a collaborative learning environment!