Spring Cloud Gateway Tutorial

Spring Cloud Gateway Tutorial thumbnail
81K
By Dhiraj 30 May, 2019

An API Gateway provides a single entry point for all the microservices running downstream. There are many gateway solutions available such as Zuul, Linkerd, Nginx, etc. but in this article, we will specifically discuss Spring Cloud Gateway - a reactive Gateway built upon Project Reactor, Spring WebFlux, and Spring Boot 2.0.

First, we will start with an introduction of Spring Cloud Gateway and then discuss the features of it that makes it the best fit as a cloud gateway. After that, we will look into the gateway flow with different examples of using predicates for routing, pre-filters, global filters to modify the request and response header and body along with Hystrix support. In the end, we will create a simple example project to demonstrate these capabilities with discovery server using Netflix, multiple microservices and Spring Cloud Gateway routing requests to these different microservices downstream.

Blocking vs Non-Blocking Gateway

A Blocking gateway such as Zuul1 requires as many threads as no of requests to handle requests coming to an API gateway and hence a lot of resources is required to handle these requests. Any extra requests simply sit in the queue and wait until a working thread completes its execution. Whereas in case of non-blocking, the main thread is always available to serve the request and other multiple threads process those requests asynchronously in the background and once the request is completely processed the response is returned. Hence non-blocking model requires a less no of resources to serve the same amount of requests as compared to blocking gateway.

Here is an example of spring cloud Netflix with Zuul as Gateway Provider.

Spring cloud gateway is a non-blocking reactive gateway similar to Zuul2 but spring cloud does not provide any out of the box integration with Zuul2.

Here is the performance benchmark of Spring Cloud gateway.

Spring Cloud Gateway Architecture

Below is a simple architecture diagram of Spring Cloud. In the later sections, we will discuss the different components involved in it.

spring-cloud-gateway-architecture

Once a request reaches to the gateway, the first thing gateway does is to match the request with each of the available route based on the predicate defined. Once, the route has matched the request moves to web handler and the filters will be applied to the request. There are many out of the box filters provided by the gateway itself to modify the request header as well as the body. Pre-filters are applied specifically to a route whereas global filters can be applied to all the route request. Global filters can be applied to perform authentication and authorization of all the request at one place.

Once the response is generated by the downstream service, the post filter can be used to modify the response such as to apply checksum in the response header to all the responses to ensure the response is not tempered due to middle man attack.

Below is an example of a route that has a predicate defined to match all the request URL with /api/v1/first/** and apply a pre-filter to rewrite the path. There is another filter applied to modify the request header and then route the request to load balanced FIRST-SERVICE.

builder.routes()
                .route(r -> r.path("/api/v1/first/**")
                        .filters(f -> f.rewritePath("/api/v1/first/(?.*)", "/${remains}")
                                        .addRequestHeader("X-first-Header", "first-service-header")
                        )
                        .uri("lb://FIRST-SERVICE/") //downstream endpoint  lb - load balanced
                        .id("queue-service"))

                .build();

Below is the equivalent .yaml configuration.

spring:
  cloud:
    gateway:
      routes:
      - id: first-service
        uri: lb://FIRST-SERVICE
        predicates:
        - Path=/api/v1/first/**
        filters:
        - RewritePath=/api/v1/first/(?.*), /$\{remains}
        - AddRequestHeader=X-first-Header, first-service-header

Handler Mapping

This is the first component where all the requests hit. Handler mapping looks into each route that is defined and tries to match with each predicate defined in the route. After the match, handler mapping routes the request to that matched route.

In the above example, we have only defined a path predicate to match the request. There are a couple of other predicates out of the box to define our route such as Host, Date/Time, Method, Headers, Query Params, Cookies, body etc.

Path Roue Predicate

With path predicate, we can match the request with URI path. For example, below route is matched against the URl - localhost:8080/api/v1/first/users and routes to load balanced first-service service.

.route(r -> r.path("/api/v1/first/**")
                        .uri("lb://FIRST-SERVICE/") 
                        .id("first-service"))

                .build();

Host Route Predicate

With the Host route predicate, we can match Host header with any given pattern.

.route(r -> r.host("**.devglan.com")
                        .uri("lb://FIRST-SERVICE/") //downstream endpoint  lb - load balanced
                        .id("first-service"))

                .build();

Date/Time Route Predicate

This predicate can be used to match the route request with any given date/time. We can use Before, After or Between to match with the date/time.

.route(r -> r.before(ZonedDateTime.now())
                        .uri("lb://FIRST-SERVICE/") //downstream endpoint  lb - load balanced
                        .id("first-service"))

                .build();

Method Route Predicate

With this predicate, we match the HTTP request method type such as GET, POST, etc.

.route(r -> r.method(HttpMethod.POST)
                        .uri("lb://FIRST-SERVICE/") //downstream endpoint  lb - load balanced
                        .id("first-service"))

                .build();

Header Route Predicate

Header route predicate is used to match the request with the header params.

.route(r -> r.header("X-first-Header", "first-service-header")
                        .uri("lb://FIRST-SERVICE/") //downstream endpoint  lb - load balanced
                        .id("first-service"))
                .build();

Query Param Route Predicate

This route predicate is used to match the request with query parameter passed in the request URI.

.route(r -> r.query("refer", "email")
                        .uri("lb://FIRST-SERVICE/") //downstream endpoint  lb - load balanced
                        .id("first-service"))
                .build();

Cookies Route Predicate

The Cookie Route Predicate Factory takes two parameters, the cookie name and a regular expression. This predicate matches cookies that have the given name and the value matches the regular expression.

.route(r -> r.cookie("chocolate", "ch.p")
                        .uri("lb://FIRST-SERVICE/")
                        .id("first-service"))
                .build();

Web Handler Filters

As discussed above, we can have pre-filters, global filters and post-flters. There are many filters that are provided by the gateway out of the box and it also provides flexibility to define our custom filters that can be either applied to a specific route or to all the request coming to the gateway.

Pre-Filters

Pre-filters are route specific which can be used to modify all the elements of the incoming request such as Headers, Path, Rate Limiting, Cookies, Hystrix, Moddify Body etc.

AddRequestHeader WebFilter

This filter is used to add extra header element in the incoming request. It takes a name and value parameter.

.route(r -> r.path("/api/v1/first/**")
                        .filters(f -> f.addRequestHeader("X-first-Header", "first-service-header"))
                        .uri("lb://FIRST-SERVICE/") 
                        .id("first-service"))
                .build();

RewritePath WebFilter

It is used to rewrite the incoming request URI path. The RewritePath GatewayFilter Factory takes a path regexp parameter and a replacement parameter. This uses Java regular expressions for a flexible way to rewrite the request path. Below is the Java config.

.route(r -> r.path("/api/v1/first/**")
                        .filters(f -> f.rewritePath("/api/v1/first/(?.*)", "/${remains}"))
                        .uri("lb://FIRST-SERVICE/")
                        .id("first-service"))
                .build();

Rate Limiting WebFilter

It is mainly used to control the amount of incoming request to the server.This filter uses a RateLimiter implementation to determine if the current request is allowed to proceed. If it is not, a status of HTTP 429 - Too Many Requests (by default) is returned.This filter takes an optional keyResolver parameter and parameters specific to the rate limiter - replenishRate, capacity, and keyResolverName

ReplenishRate - Defines number of requests per second allowed.

Capacity - Defines bursting capacity that is allowed. 2 consecutive bursts will result in dropped requests (HTTP 429 - Too Many Requests)

KeyResolverName - Custom implementation of KeyResolver interface.

.route(r -> r.path("/api/v1/first/**")
                        .filters(f -> f.requestRateLimiter().configure(c -> c.setRateLimiter(customRateLimiter)))
                        .uri("lb://FIRST-SERVICE/")
                        .id("first-service"))
                .build();

Here, customRateLimiter is the implementation of KeyResolver interface.

Hystrix WebFilter

The Hystrix GatewayFilter allows you to introduce circuit breakers to your gateway routes, protecting your services from cascading failures and allowing you to provide fallback responses in the event of downstream failures. The Hystrix GatewayFilter Factory requires a single name parameter, which is the name of the HystrixCommand.

.route(r -> r.path("/api/v1/first/**")
                        .filters(f -> f.hystrix(h -> h.setName("Hystrix").setFallbackUri("forward:/fallback")))
                        .uri("lb://FIRST-SERVICE/")
                        .id("first-service"))
                .build();
@RestController
@RequestMapping("/fallback")
public class FallbackController {

    @GetMapping("/")
    public ResponseEntity fallback(){
        return ResponseEntity.ok(Arrays.asList());
    }
}

Modify Request Body Filter

This filter can be used to modify the request body before it is sent downstream by the Gateway.

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org")
            .filters(f -> f.prefixPath("/httpbin")
                .modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,
                    (exchange, s) -> return Mono.just(new Hello(s.toUpperCase())))).uri(uri))
        .build();
}

static class Hello {
    String message;

    public Hello() { }

    public Hello(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

Global Filter

Global filters cana be applied conditionally to all routes. The different global filters are Netty Router, Web Sockets, Load Balancer, Metrics.

Post Filters

Post filters are used to add, modify, remove or rewrite response header or body.

Add Response Header Filter

.route(r -> r.path("/api/v1/first/**")
                        .filters(f -> f.addResponseHeader("payload-size", "12"))
                        .uri("lb://FIRST-SERVICE/")
                        .id("first-service"))
                .build();

Remove Response Header Filter

.route(r -> r.path("/api/v1/first/**")
                        .filters(f -> f.removeResponseHeader("payload-size"))
                        .uri("lb://FIRST-SERVICE/")
                        .id("first-service"))
                .build();

Conclusion

In this article, we discussed about the spring cloud gateway and its different components such as routes, predicates, filters with example. In the next tutorial of Spring Cloud Gateway Example we will implement these theories and build an application with microservice architecture using Spring Cloud Gateway and demonstrate working examples on web filters to modify request and response body.

Share

If You Appreciate This, You Can Consider:

We are thankful for your never ending support.

About The Author

author-image
A technology savvy professional with an exceptional capacity to analyze, solve problems and multi-task. Technical expertise in highly scalable distributed systems, self-healing systems, and service-oriented architecture. Technical Skills: Java/J2EE, Spring, Hibernate, Reactive Programming, Microservices, Hystrix, Rest APIs, Java 8, Kafka, Kibana, Elasticsearch, etc.

Further Reading on Spring Cloud