Spring Cloud Gateway Example

Spring Cloud Gateway Example thumbnail
91K
By Dhiraj 09 August, 2019

In the last article, we looked into Spring Cloud Gateway and discussed its core concepts and capabilities as a non-blocking API Gateway. In this article, we will use those concepts to develop an end to end microservice architecture based application using spring cloud. In the process, we will use spring cloud gateway as a gateway provider, Netflix Eureka as a discovery server with circuit breaker pattern using Netflix Hystrix.

In this implementation, we will have 2 different spring boot based microservices as first-service and second-service. These services will register themselves to the discovery server. Spring cloud gateway will use Netflix client API to discover the service and route the request to load balanced downstream services.

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

Discovery Server Implementation

In a microservice architecture, service discovery is one of the key tenets. Service discovery automates the process of multiple instance creations on demand and provides high availability of our microservices.

Below is the pom.xml and application.yml file required for this integration. You can visit my previous article for the complete integration of Netflix Eureka with Spring Cloud

pom.xml

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>
<repositories>
	<repository>
		<id>spring-milestones</id>
		<name>Spring Milestones</name>
		<url>https://repo.spring.io/milestone</url>
	</repository>
</repositories>
<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>
application.yml
spring:
  application:
    name: discovery-service

eureka:
  client:
    eureka-server-connect-timeout-seconds: 5
    enabled: true
    fetch-registry: false
    register-with-eureka: false

server:
  port: 8761

Spring Cloud Gateway Implementation

Project Setup

First, we will generate a sample spring boot project from https://start.spring.io and import into workspace. The selected dependencies are Gateway, Hystrix and Actuator.

spring-cloud-gateway-project-strct

We will also add spring-cloud-starter-netflix-eureka-client dependency in our pom.

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

Spring Cloud Route Configuration

Route is the basic building block 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.

Spring Cloud Gateway provides many built-in Route Predicate Factories such as Path, Host, Date/Time, Method, Header etc. We can use these built-in route with conjuction with and() or or() to define our routes. 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 and the request is routed to matched route.

You can follow my another article for the complete spring cloud gateway route predicates..

Below is our route configuration. We have 2 different routes defined for our 2 microservices - first-service and second-service.

Here, we have defined functional routes with Spring WebFlux but you are free to use traditional Spring MVC style routes. You can visit this article for both functional and traditional style API definition.

BeanConfig.java
package com.devglan.gatewayservice;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {

    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/api/v1/first/**")
                        .filters(f -> f.rewritePath("/api/v1/first/(?.*)", "/${remains}")
                                .addRequestHeader("X-first-Header", "first-service-header")
                                .hystrix(c -> c.setName("hystrix")
                                        .setFallbackUri("forward:/fallback/first")))
                        .uri("lb://FIRST-SERVICE/")
                        .id("first-service"))

                .route(r -> r.path("/api/v1/second/**")
                        .filters(f -> f.rewritePath("/api/v1/second/(?.*)", "/${remains}")
                                .hystrix(c -> c.setName("hystrix")
                                        .setFallbackUri("forward:/fallback/second")))
                        .uri("lb://SECOND-SERVICE/")
                        .id("second-service"))
                .build();
    }

}

In the above configuration, the first route is matched for a path that has a predicate defined to match all the request URL with /api/v1/first/**. After these multiple filters such as rewritePath, addRequestHeader etc is applied. These are in-built filters provided by the gateway out of the box.

rewritePath filter takes a path regexp parameter and a replacement parameter.

The AddRequestHeader GatewayFilter Factory takes a name and value parameter and adds the configured header param in the request.

The Hystrix GatewayFilter Factory requires a single name parameter, which is the name of the HystrixCommand. The request will be forwarded to the controller matched by the fallbackUri parameter.

Below is an equivalent application.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
			- name: Hystrix
				args:
				  name: hystrix
				  fallbackUri: forward:/fallback/first

Spring Cloud Gateway Application Config

Below is our application.yaml file.

application.yaml
hystrix.command.fallbackcmd.execution.isolation.thread.timeoutInMilliseconds: 2000
spring:
  application:
    name: api-gateway

server:
  port: 8088

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka
    register-with-eureka: false
  instance:
    preferIpAddress: true

management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

Hystrix Fallback Command

Below is our controller implementation whose endpoints will be called on failure of our microservices.

HystrixController.java
package com.devglan.gatewayservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/fallback")
public class HystrixController {

    @GetMapping("/first")
    public String firstServiceFallback(){
        return "This is a fallback for first service.";
    }

    @GetMapping("/second")
    public String secondServiceFallback(){
        return "Second Server overloaded! Please try after some time.";
    }
}

Now, let us define our spring boot main class.

GatewayServiceApplication.java
package com.devglan.gatewayservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(GatewayServiceApplication.class, args);
	}

}

Microservices Implementation

First Service

These services are very simple implementation with only one controller defined for demo purpose.

FirstController.java
package com.devglan.gatewayservice.controller;

import org.springframework.web.bind.annotation.*;

@RestController
public class FirstController {

    @GetMapping("/test")
    public String test(@RequestHeader("X-first-Header") String headerValue){
        return headerValue;
    }

}

application.yaml
spring:
  application:
    name: first-service
server:
  port: 8086

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

FirstServiceApplication.java
package com.devglan.gatewayservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class FirstServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(FirstServiceApplication.class, args);
	}

}

Second Service Implementation

SecondController.java

@RestController
public class SecondController {

    @GetMapping("/second")
    public String test(@RequestHeader("X-second-Header") String headerValue){
        return headerValue;
    }

}

application.yaml
spring:
  application:
    name: second-service
server:
  port: 8087

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

Adding CORS Configuration in Spring Cloud Gateway

While making API calls from a browser app with cross-origin, we frequently get errors as Cross-Origin Request Blocked and the API call fails. If you have spring cloud gateway as a Gateway provider, then you can define below filter to allow such Cross-origin request.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;

/**
 * @author Dhiraj Ray
 *
 */
@Configuration
@EnableWebFlux
public class CORSFilter implements WebFluxConfigurer {

   @Override
   public void addCorsMappings(CorsRegistry registry) {
      registry.addMapping("/**")
            .allowCredentials(true)
            .allowedOrigins("*")
            .allowedHeaders("*")
            .allowedMethods("*");
   }

   @Bean
   public CorsWebFilter corsWebFilter() {
      CorsConfiguration corsConfiguration = new CorsConfiguration();
      corsConfiguration.setAllowCredentials(true);
      corsConfiguration.addAllowedHeader("*");
      corsConfiguration.addAllowedMethod("*");
      corsConfiguration.addAllowedOrigin("*");
      UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
      corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
      return new CorsWebFilter(corsConfigurationSource);
   }
}

Serving Static Content Spring Cloud Gateway

The best practice to host your images or other static contents is the CDN but it is a very common practice to host static files at the Gateway level. Static files can be any image or .html file. As spring cloud gateway is developed upon spring web flux, we can use the spring web flux way to serve our static content from spring cloud gateway.

By default, Spring Boot serves static content from /public, /static, /resources, /META-INF/resources. Hence, without any extra configuration, you can place your static content in any one of these folders and it can be directly accessed with a HTTP request.

There are other different ways too to access these static resources and the complete implementation can be found in my another article here.

Conclusion

In this article, we discussed about spring cloud gateway and created a demo application using it. The source code can be downloaded from github here.

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