Introduction
In a distributed environment, services need to communicate with each other. However, this is inter-service communication. We also have use-cases where a client outside our domain wants to hit our services for the API. So, either we can expose the address of all our microservices which can be called by clients OR we can create a Service Gateway which routes the request to various microservices and responds to the clients.
Creating a API Gateway is much better approach here. There are two major advantages −
- The security for each individual services does not need to maintained.
- And, cross-cutting concerns, for example, addition of meta-information can be handled at a single place.
Netflix Zuul and Spring Cloud Gateway are two well-known Cloud Gateways which are used to handle such situations. In this tutorial, we will use Spring Cloud Gateway.
Spring Cloud Gateway – Dependency Setting
Let us use the case of Restaurant which we have been using. Let us add a new service (gateway) in front of our two services, i.e., Restaurant services and Customer Service. First, let us update the pom.xml of the service with the following dependency −
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
And then, annotate our Spring application class with the correct annotation, i.e., @EnableDiscoveryClient.
package com.javahubpoint;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class RestaurantGatewayService{
public static void main(String[] args) {
SpringApplication.run(RestaurantGatewayService.class, args);
}
}
We are annotating with @EnableDiscoveryClient because we want to use Eureka service discovery to get the list of hosts which are serving a particular use-case
Dynamic Routing with Gateway
The Spring Cloud Gateway has 3 important parts to it. Those are −
- Route − These are the building blocks of the gateway which contain URL to which request is to be forwarded to and the predicates and filters that are applied on the incoming requests.
- Predicate − These are the set of criteria which should match for the incoming requests to be forwarded to internal microservices. For example, a path predicate will forward the request only if the incoming URL contains that path.
- Filters − These act as the place where you can modify the incoming requests before sending the requests to the internal microservices or before responding back to the client.
Let us write a simple configuration for the Gateway for our Restaurant and Customer service.
spring:
application:
name: restaurant-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: customers
uri: lb://customer-service
predicates:
- Path=/customer/**
- id: restaurants
uri: lb://restaurant-service
predicates:
- Path=/restaurant/**
server:
port: ${app_port}
eureka:
client:
serviceURL:
defaultZone: http://localhost:8900/eureka
Points to note about the above configuration −
- We have enabled the discovery.locator to ensure that the gateway can read from the Eureka server.
- We have used Path predicated here to route the request. What this means is that any request which begins with /customer would be routed to Customer Service and for /restaurant, we will forward that request to Restaurant Service.
Now let us setup other services prior to the Gateway service −
- Start the Eureka Server
- Start the Customer Service
- Start the Restaurant Service
Now, let us compile and execute the Gateway project. We will use the following command for the same −
java -Dapp_port=8084 -jar .\target\spring-cloud-gateway-1.0.jar
Once this is done, we have our Gateway ready to be tested on port 8084. Let’s first hit http://localhost:8084/customer/1 and we see the request is correctly routed to Customer Service and we get the following output −
{
"id": 1,
"name": "Jane",
"city": "DC"
}
And now, hit our restaurant API, i.e., http://localhost:8084/restaurant/customer/1 and we get the following output −
{
"id": 1,
"name": "Pandas",
"city": "DC"
},
{
"id": 3,
"name": "Little Italy",
"city": "DC"
}
]
This means that both the calls were correctly routed to the respective services.
Predicates & Filters Request
We had used Path predicate in our above example. Here are a few other important predicates −
Predicate |
Description |
Cookie predicate (input: name and regex) |
Compares the cookie with the ‘name’ to the ‘regexp’ |
Header predicate (input: name and regex) |
Compares the header with the ‘name’ to the ‘regexp’ |
Host predicate (input: name and regex) |
Compares the ‘name’ of the incoming to the ‘regexp’ |
Weight Predicate (input: Group name and the weight) |
Weight Predicate (input: Group name and the weight) |
Filters :-
It is used to add/remove data from the request before sending the data to the downstream service or before sending the response back to the client.
Following are a few important filters for adding metadata.
Filter |
Description |
Add request header filter (input: header and the value) |
Add a ‘header’ and the ‘value’ before forwarding the request
downstream. |
Add response header filter (input: header and the value) |
Add a ‘header’ and the ‘value’ before forwarding the request
upstream that is to the client. |
Redirect filter (input: status and URL) |
Adds a redirect header along with the URL before passing over o
the downstream host. |
ReWritePath (input: regexp and replacement) |
This is responsible for rewriting the path by replacing the
‘regexp’ matched string with the input replacement. |
The exhaustive list for filters and predicates is present at Writing Custom Predicates and Filters :: Spring Cloud Gateway
Monitoring
For monitoring of the Gateway or for accessing various routes, predicates, etc., we can enable the actuator in the project. For doing that, let us first update the pom.xml to contain the actuator as a dependency.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
For monitoring, we will use a separate application property file which would contain flags to enable the actuator. So, here is how it would look like −
spring:
application:
name: restaurant-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: customers
uri: lb://customer-service
predicates:
- Path=/customer/**
- id: restaurants
uri: lb://restaurant-service
predicates:
- Path=/restaurant/**
server:
port: ${app_port}
eureka:
client:
serviceURL:
defaultZone: http://localhost:8900/eureka
management:
endpoint:
gateway:
enabled: true
endpoints:
web:
exposure:
include: gateway
Now, to list all the routes, we can hit: http://localhost:8084/actuator/gateway/routes
[
{
"predicate": "Paths: [/customer/**], match trailing slash: true",
"route_id": "customers",
"filters": [],
"uri": "lb://customer-service",
"order": 0
},
{
"predicate": "Paths: [/restaurant/**], match trailing slash: true",
"route_id": "restaurants",
"filters": [],
"uri": "lb://restaurant-service",
"order": 0
}
]
Other important APIs for monitoring −
API |
Description |
GET /actuator/gateway/routes/{id} |
Get information about a particular route |
POST /gateway/routes/{id_to_be assigned} |
Add a new route to the Gateway |
DELETE /gateway/routes/{id} |
Remove the route from Gateway |
POST /gateway/refresh |
Remove all the cache entries |