Table of Contents
ToggleIntroduction
API gateways have become a crucial component of modern microservices architecture, providing a single entry point for clients to access multiple services. It is a popular choice among developers for building scalable, secure, and maintainable API gateways. In this guide, we will take you through the step-by-step process of mastering API gateway development with Spring Cloud Gateway. We will also develop an example code while going through this article.
What is an API Gateway?
An API gateway acts as a single entry point for clients to access multiple services, providing features such as load balancing, rate limiting, and caching. The security layer is provided by filtering incoming requests, validating authentication credentials, and encrypting sensitive data.
Benefits of Using Spring Cloud Gateway
It offers several benefits, including:
- Scalability: Designed to handle heavy traffic loads with ease and deliver exceptional performance, even during peak times
- Security: Provides robust features for authentication, authorization, and encryption
- Flexibility: Supports multiple routing strategies and filters
- Maintainability: Easy to configure and deploy
Prerequisites
Before you begin, ensure that:
- You have a foundational understanding of Java and Spring Boot. If not please read my articles on Spring Boot
- You have Maven or Gradle installed on your system. You can read my article on Maven if you are new to Maven here
- You have a IDE (e.g. Eclipse, IntelliJ IDEA) with Spring Tool Suite
Step-by-Step Tutorial: Building a Simple API Gateway with Spring Cloud Gateway
Step 1: Create a New Spring Boot Project
Open your IDE and create a new Spring Boot project using the following command:
spring init --build=maven --name=my-api-gateway
Alternatively, you can use the Spring Initializr web interface to create a new project.
Step 2: Add Dependencies to Your ‘pom.xml’ File
Add the following dependencies to your `pom.xml` file to include Cloud Gateway in your project:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
Step 3: Configure it in Your ‘application.yml’ File
Configure the spring cloud gateway settings in your `application.yml` file as follows:
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
- id: hello-world-service
uri: https://hello-world-service.com/
predicates:
- Path=/hello/**
Step 4: Create a Route Configuration Class
Create a new class, ‘HelloWorldRouteConfiguration.java’, to configure the route for your service:
package com.aneesh.spring.cloud.gateway;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
public class HelloWorldRouteConfiguration {
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("hello-world-service", r -> r.path("/hello/**")
.uri("https://hello-world-service.com/"))
.build();
}
}
Step 5: Run Your Application
Run your application using the following command:
mvn spring-boot:run
You should now be able to access your service by visiting `http://localhost:8080/hello` in your browser.
Understanding Route and Filter
In Spring Cloud Gateway, routes are defined as a combination of predicates and filters. Predicates determine which incoming requests should be routed to a specific filter, while filters perform actions
on the request before forwarding it to the downstream service.
Step 6: Create a Custom Filter
package com.aneesh.spring.cloud.gatewa;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class CustomFilter implements GlobalFilter {
final Logger logger =
LoggerFactory.getLogger(AuthFilter.class);
@Override
public Mono<Void> filter(
ServerWebExchange exchange,
GatewayFilterChain chain) {
logger.info("Global Pre Filter executed");
return chain.filter(exchange);
}
}
Step 7: Apply the Filter to Your Route
Apply the filter to your route in the `application.yml` file:
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
- id: hello-world-service
uri: https://hello-world-service.com/
predicates:
- Path=/hello/**
filters:
-name: CustomFilter
Handling Errors and Logging
Spring Cloud Gateway provides built-in support for handling errors and logging. You can use the `ErrorResponse` class to return a custom error response, and the `LoggingGatewayFilterFactory` class to log
incoming requests.
Step 8: Create an Error Response Class
Create a custom error response class:
package com.aneesh.spring.cloud.gatewa;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseCookie;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class ErrorResponse implements ServerResponse {
private int status;
private String message;
public ErrorResponse(int status) {
this.status = status;
}
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public HttpStatusCode statusCode() {
return HttpStatusCode.valueOf(status);
}
@Override
public int rawStatusCode() {
return status;
}
@Override
public HttpHeaders headers() {
return HttpHeaders.readOnlyHttpHeaders(HttpHeaders.EMPTY);
}
@Override
public MultiValueMap<String, ResponseCookie> cookies() {
return null;
}
@Override
public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
return null;
}
}
Step 9: Apply the Logging Filter to Your Route
Create a GatewayFilter Factory which will log before the request is forwarded and after it is executed
package com.aneesh.spring.cloud.gatewa;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
public class LoggingGatewayFilterFactory extends
AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> {
final Logger logger =
LoggerFactory.getLogger(LoggingGatewayFilterFactory.class);
public LoggingGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// Pre-processing
if (config.preLogger) {
logger.info("Pre LoggingGatewayFilter message: "
+ config.preMessage + "request from:"+exchange.getRequest().getRemoteAddress());
}
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
// Post-processing
if (config.postLogger) {
logger.info("Post LoggingGatewayFilter message: "
+ config.postMessage+ "response headers: "
+exchange.getResponse().getHeaders().getAccept());
}
}));
};
}
public static class Config {
private String preMessage;
private boolean preLogger;
private String postMessage;
private boolean postLogger;
}
}
Apply the logging filter to your route in the `application.yml` file:
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
- id: hello-world-service
uri: https://hello-world-service.com/
predicates:
- Path=/hello/**
filters:
-name: LoggingGatewayFilterFactory
args:
preMessage: before logging
preLogger: true
postMessage: after logging
postLogger: true
Securing Your API Gateway with OAuth 2.0 and JWT
To secure your API gateway using OAuth 2.0 and JWT, you can use the `spring-cloud-starter-oauth2` dependency to include OAuth 2.0 support in your project.
Step 10: Configure OAuth 2.0 Settings
Configure the OAuth 2.0 settings in your `application.yml` file:
spring:
application:
name: spring-cloud-gateway
cloud:
oauth2:
client:
registration:
my-client:
clientId: my_client_id
clientSecret: my_client_secret
redirectUri: https://my-redirect-uri.com/
Step 11: Create an OAuth 2.0 Filter
Create a custom filter to validate authentication credentials using OAuth 2.0:
package com.aneesh.spring.cloud.gatewa;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AuthFilter implements GlobalFilter {
@Override
public Mono<> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
// Validate authentication credentials here
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return Mono.just(new ErrorResponse(401));
}
// Validate authentication credentials here using OAuth 2.0
return chain.filter(exchange);
}
}
Step 12: Implementing Spring Cloud gateway Rate Limiting**
To add rate limiting to our API gateway, we’ll use a library called Spring Cloud Gateway’s `RateLimiter` feature. Create a new file called `RateLimitConfig.java` with the following code:
package com.aneesh.spring.cloud.gatewa;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.cloud.gateway.filter.factory.RateLimiterFactory;
@Component
public class RateLimitConfig {
@Bean
public RateLimiterFactory rateLimiterFactory() {
return new RateLimiterFactory(10, 60);
}
}
In this configuration, we’ve enabled a rate limiter that allows up to 10 requests per minute for each user.
Deploying the application to Production
To deploy your API gateway to production, you can use a containerization tool such as Docker to package and deploy your application.
Step 12: Create a Dockerfile
Create a `Dockerfile` in the root of your project:
FROM openjdk:8-jdk-alpine
WORKDIR /app
COPY . .
RUN mvn clean package
EXPOSE 8080
CMD ["java", "-jar", "my-api-gateway.jar"]
Step 13: Build a Docker Image
Build a Docker image using the following command:
docker build -t my-api-gateway .
Step 14: Push the Image to a Container Registry
Push the image to a container registry such as Docker Hub or Amazon ECR.
docker tag appname dockerhubusername/appname
docker login
docker push dockerhubusername/appname
Step 15: Deploy Your Application to Production
Deploy your application to production by running the following command:
docker run -p 8080:8080 my-api-gateway
Spring Cloud Gateway vs Zuul
When it comes to building a robust API gateway for microservices, the choice between Spring Cloud Gateway and Zuul can be daunting. Consider this example of routing traffic to multiple services using Gateway’s
@Bean
public Route routes() {
return new RouteBuilder()
.route("service-a", r -> r.request().uri("http://localhost:8081"))
.route("service-b", r -> r.request().uri("http://localhost:8082"))
.build();
}
Compare this to Zuul’s more verbose configuration using a ZuulProperties bean:
zuul:
routes:
service-a:
path: /api/**
serviceId: service-a
As you can see, Gateway makes it much easier to define and manage routes.
Another key difference between Cloud Gateway and Zuul lies in their handling of filters. In Gateway, you can use a FilterFactoryBean as mentioned above to create and apply filters like this:
@Bean
public FilterFactoryBean loggingFilter() {
return new FilterFactoryBean()
.addFilter(new LoggingFilter());
}
Zuul, on the other hand, uses a pre and post filter mechanism that’s more cumbersome to work with. For example:
zuul:
filters:
pre:
- logging.filter1
- logging.filter2
As you can see, Gateway makes it much simpler to apply filters to your API requests.
Congratulations! You have now successfully mastered API gateway development with Spring Cloud Gateway.
FAQ
Q: What is the difference between a traditional proxy server and an API gateway?
A: A traditional proxy server primarily forwards incoming requests to a backend service, while an API gateway provides a layer of abstraction between clients and services. It handles features such as
authentication, rate limiting, caching, and error handling.
Q: How does Spring Cloud Gateway handle load balancing?
A: Spring Cloud Gateway uses the Hystrix library for load balancing. You can configure multiple instances of the same service to be used for load-balancing purposes. This ensures that your application remains available even if one instance fails or becomes unresponsive.
Q: Can I use Spring Cloud Gateway with other web frameworks such as Jersey?
A: Yes, you can use Spring Cloud Gateway with other web frameworks like Jersey. Spring Cloud Gateway provides a flexible architecture that allows integration with multiple web frameworks through its filters and gateway interfaces.
Q: How do I handle errors in my API gateway using Spring Cloud Gateway?
A: You can create custom error handlers to return specific error responses based on the type of error encountered. Spring Cloud Gateway also provides built-in support for handling errors, such as returning HTTP status codes and including error messages in response bodies.
Q: Is it possible to secure my API gateway with OAuth 2.0 and JWT?
A: Yes, you can use the spring-cloud-starter-oauth2 dependency to include OAuth 2.0 support in your Spring Cloud Gateway application. This allows for secure authentication of clients using tokens issued by an authorization server. You can then use these tokens to validate client identities in subsequent requests.
Q: What’s the main difference between an API Gateway and a Load Balancer?
A: API Gateway vs Load Balancer is the most common question among microservice developers. The answer is while both are used to manage traffic, an API Gateway is like a concierge who greets you at the door of your favorite restaurant, while a Load Balancer is more like a bouncer who lets in the right number of people. The Gateway handles requests, applies security and caching, and routes them to the right service, whereas the Load Balancer just spreads the load across multiple servers.