Learnitweb

Building Edge Server using Spring Cloud Gateway

In this tutorial, we’ll build an edge server using Spring Cloud Gateway.

Dependency

To create an edge server using Spring Cloud Gateway, following is the dependency:

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

We’ll also need the spring-cloud-starter-netflix-eureka-client dependency as our gateway server is going to register with Eureka server. Following is our complete pom.xml:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.4.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.learnitweb</groupId>
	<artifactId>gatewayserver</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>gatewayserver</name>
	<description>Edge server for microservices</description>
	<url/>
	<licenses>
		<license/>
	</licenses>
	<developers>
		<developer/>
	</developers>
	<scm>
		<connection/>
		<developerConnection/>
		<tag/>
		<url/>
	</scm>
	<properties>
		<java.version>17</java.version>
		<spring-cloud.version>2024.0.0</spring-cloud.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</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>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Configuration

Following is the complete configuration file (application.yml) of the gateway server:

server:
  port: 8072

spring:
  application:
    name: "gatewayserver"
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      
eureka:
  client:
    fetchRegistry: true
    registerWithEureka: true
    service-url:
      defaultZone: http://localhost:8070/eureka/
  instance:
    hostname: localhost
    prefer-ip-address: true

management:
  endpoints:
    web:
      exposure:
        include: "*" # Use "health,info" in production
  endpoint:
    gateway:
      access: unrestricted
  info:
    env:
      enabled: true
  • spring.application.name: “gatewayserver”
    • This sets the name of the Spring Boot application to "gatewayserver".
    • The application name is often used for identification in logs, monitoring systems, and service discovery when working with tools like Eureka or Consul.
  • spring.cloud.gateway.discovery.locator.enabled: true
    • Purpose: This enables the gateway to dynamically route requests to services registered in a service discovery mechanism (e.g., Eureka, Consul).
    • The Discovery Locator functionality allows the API Gateway to automatically map routes to services without explicitly defining routes in the configuration file.
    • When enabled:
      • It discovers all the services registered in the discovery service.
      • It creates default routes for each of the discovered services.

How it Works

  • Service Discovery Integration:
    • The gateway connects to a service discovery tool (like Eureka).
    • All microservices registered with the discovery server are visible to the gateway.
  • Dynamic Routing:
    • The gateway generates routes automatically based on the registered service names.
    • For example:
    • If a service named inventory-service is registered, the gateway would map a route like:
      http://<gateway-host>:<port>/inventory-service/**
      This forwards requests to the inventory-service.
  • Simplifies Configuration:
    • No need to manually configure routes for each service.
    • This is especially useful in environments with dynamic scaling or frequent service updates.

Demo of Edge Server with default routing config

After the gateway server is set. Start the Eureka server, Gateway server and other microservices. Once everything is started, the Eureka server dashboard looks like the following:

All routes on the gateway server can be seen at the following endpoint:

http://localhost:8072/actuator/gateway/routes

[
  {
    "predicate": "Paths: [/PRODUCTSERVICE/**], match trailing slash: true",
    "metadata": {
      "jmx.port": "50265",
      "management.port": "8071"
    },
    "route_id": "ReactiveCompositeDiscoveryClient_PRODUCTSERVICE",
    "filters": [
      "[[RewritePath /PRODUCTSERVICE/?(?\u003Cremaining\u003E.*) = '/${remaining}'], order = 1]"
    ],
    "uri": "lb://PRODUCTSERVICE",
    "order": 0
  },
  {
    "predicate": "Paths: [/GATEWAYSERVER/**], match trailing slash: true",
    "metadata": {
      "jmx.port": "50233",
      "management.port": "8072"
    },
    "route_id": "ReactiveCompositeDiscoveryClient_GATEWAYSERVER",
    "filters": [
      "[[RewritePath /GATEWAYSERVER/?(?\u003Cremaining\u003E.*) = '/${remaining}'], order = 1]"
    ],
    "uri": "lb://GATEWAYSERVER",
    "order": 0
  },
  {
    "predicate": "Paths: [/INVENTORYSERVICE/**], match trailing slash: true",
    "metadata": {
      "jmx.port": "50283",
      "management.port": "8073"
    },
    "route_id": "ReactiveCompositeDiscoveryClient_INVENTORYSERVICE",
    "filters": [
      "[[RewritePath /INVENTORYSERVICE/?(?\u003Cremaining\u003E.*) = '/${remaining}'], order = 1]"
    ],
    "uri": "lb://INVENTORYSERVICE",
    "order": 0
  }
]

Now, we’ll access the inventory service endpoint using the gateway server. Following is the endpoint:

localhost:8072/PRODUCTSERVICE/api/inventory/car

Note that here, 8072 is the port of the gateway server. We are calling the endpoint of PRODUCTSERVICE.

@RestController
@RequestMapping("/api")
public class InventoryController {

	@Autowired
	private InventoryFeignClient inventoryFeignClient;

	@GetMapping("/inventory/{name}")
	public ResponseEntity<?> getProductInventory(@PathVariable String name) {
		ResponseEntity<InventoryDto> result = inventoryFeignClient.getProductInventory(name);
		return result;

	}

}

PRODUCTSERVICE calls the INVENTORYSERVICE using the Feign client.

Following is the sample output:

{
    "productName": "some car",
    "quantityRemaining": 100
}

Change in Gateway server to accept service name in lowercase

Earlier we used the capital letters while using the service:

localhost:8072/PRODUCTSERVICE/api/inventory/car

Bu that should not be the case. We should allow service names to be in lowercase. To do that, add lowerCaseServiceId: true to the application.yml of gateway server.

spring:
  application:
    name: "gatewayserver"
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lowerCaseServiceId: true