A look into Spring Cloud Gateway!
If you're (or thinking of) using microservices, you should then be familiar, or at least bumped into the API gateway pattern
. This pattern is described with great details by Chris Richardson at microservices.io. An API gateway acts as a single entry point into a system and allows to centralize many cross cutting concerns such as: routing mechanisms, cache management, security, monitoring/metrics and resiliency.
In this article, we'll explore a new project from the Spring cloud suite : Spring Cloud Gateway project, built on Spring 5, Spring Boot 2 and Project Reactor.
Spring Cloud gateway
When it comes to choosing an API gateway for your microservices, there are a variety of options: Zuul from netflix, Kong, Nginx, HAProxy, Traefik, cloud vendor's solutions such as Amazon API Gateway or Google Cloud Endpoints and (the new) Spring Cloud gateway from Pivotal.
Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs. When it receives request, Spring Cloud Gateway forwards it to a Gateway Handler Mapping, which determines what should be done with requests matching a specific route.
Instead of taking a traditional approach and listing all the features, I rather prefer to build a small , microservices based project, that showcase and describe it usage
Getting started
The minimalist project, openly available on [github],(https://github.com/aboullaite/spring-cloud-gateway) is composed of 2 services (BookStore and MovieStore), a gateway (based on spring cloud gateway obviously), Service Discovery (Eureka server) and the Hystrix dashboard. The project also requires a redis instance running and listening, to make use of the request.rate limit feature of Spring Cloud Gateway,
Routing
Spring Cloud Gateway supports 2 forms of to create routes: pure java (using RouteLocator
) or config files. Our project make use of the second approach!
Let's take a look at a snippet from the application.yml
from the gateway
module:
spring:
cloud:
gateway:
routes:
- id: movie-store
uri: lb://movie-store
predicates:
- Path=/api/movies/**
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@userKeyResolver}'
redis-rate-limiter.replenishRate: 2
redis-rate-limiter.burstCapacity: 2
- RewritePath=/api/(?<movies>.*), /$\{movies}
- AddResponseHeader=X-Some-Header, aboullaite.me
This configuration tells Spring Cloud Gateway to route all requests made to the gateway at /api/movies/
to the movie-store
service. Notice the use of predicates and filters, so we can route handle based on certain conditions as well as alter the request/response as needed.
Worth nothing to mention that there are 3 main building blocks for Spring Cloud Gateway:
- Route: the primary API of the gateway. It is defined by an ID, a destination URI, a collection of predicates and a collection of filters. A route is matched if aggregate predicate is true.
- Predicate: Java 8 Function Predicate. it enables to match on anything from the HTTP request, such as headers or parameters.
- Filter: Standard Spring Framework's
WebFilter
, where requests and responses can be modified before or after sending the downstream request.
As you might have already noticed, in the snippet above we used 3 filters
:
AddResponseHeader
filter to addX-Some-Header
header containing the valueaboullaite.me
in the response.RewritePath
filter to rewrite the request path from/api/movies/**
to/movies/**
RequestRateLimiter
to implement request rate limiter.
To read more about Route filters, refer to this link.
Rate limiting
Rate limiting is mainly used to control the rate of traffic sent or received on the network. It helps to prevent decrease in the service quality or even outage due to high traffic and improve the reliability of the API. There are different types of rate limiting, each used to target a specific need.
Unlike zuul
which doesn't have an integrated out of the box solution for rate limiting, Spring Cloud Gateway comes with a RequestRateLimiter
filter to determine if the current request is allowed to proceed. The only supported implementation for now is redis
, that requires the use of the spring-boot-starter-data-redis-reactive
Spring Boot starter.
The RequestRateLimiter
takes an optional keyResolver
parameter and parameters specific to the rate limiter. Notice that the key-resolver
takes a SpEL expression referencing the bean with the name userKeyResolver
. This bean implements the KeyResolver
interface that help to derive the key for limiting requests:
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just("fero");
}
We've also declared 2 additional params:
replenishRate
: represents how many requests per second a user is allowed to do.burstCapacity
: defines the maximum number of requests a user is allowed to do in a single second.
Furthermore, this filter adds additional headers that indicate what is the rate limit for a client and how quickly it’s being used:
X-RateLimit-Replenish-Rate
indicates thereplenishRate
value.X-RateLimit-Burst-Capacity
indicates theburstCapacity
value.X-RateLimit-Remaining
indicates how many calls you have remaining.
When request submissions exceed the replenish rate and burst limits, the gateway fails the limit-exceeding requests and returns 429 Too Many Requests
error responses to the client.
Using Hystrix
Circuit Breaker is an interesting pattern that improve your API reliability and prevent a network or service failure from cascading to other services and clients. Spring Cloud Gateway comes with Hystrix support, a Netflix library that implements the circuit breaker pattern.
hystrix.command.fallbackcmd.execution.isolation.thread.timeoutInMilliseconds: 2000
spring:
cloud:
gateway:
routes:
- id: book-store
uri: lb://book-store
predicates:
- Path=/api/books/**
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@userKeyResolver}'
redis-rate-limiter.replenishRate: 2
redis-rate-limiter.burstCapacity: 2
- RewritePath=/api/(?<books>.*), /$\{books}
- name: Hystrix
args:
name: booksFallbackCommand
fallbackUri: forward:/fallback/books
The /books/danger
endpoint in the books-store
module simulate an endpoint with poor performance that takes a long time to send its response. We additionally wrap the route that uses this API in a HystrixCommand
: booksFallbackCommand
. Now when the Hystrix wrapped route times out (i.e takes more than 2s) it will call /fallback/books
endpoint in the Gateway app, which simply return an empty list:
@RestController
@RequestMapping("/fallback")
public class FallbackController {
@GetMapping("/books")
public ResponseEntity<List> booksFallback(){
return ResponseEntity.ok(Arrays.asList());
}
}
Note that this example also demonstrates Spring Cloud Netflix Ribbon load-balancing via the lb
prefix on the destination URI.
That's it for this article! We explored some of the features and components that are part of Spring Cloud Gateway, mainly the rate limit and circuit breaker features! The project I've used can be found on github.