Open In App

Rate Limiting in Spring WebFlux

Last Updated : 16 Jul, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Rate limiting is a crucial technique to control the amount of incoming traffic to the server. This prevents abuse, ensures fair resource usage, and protects against potential Denial of Service (DOS) attacks. In the Spring WebFlux, rate limiting can be implemented effectively using the Spring Cloud Gateway. This article will guide setting up the rate limiting in the Spring WebFlux application.

Rate limiting restricts the number of requests the client can make to the server within the specified period. In Spring WebFlux, it can be achieved using Spring Cloud Gateway with Redis as the backing store to track the request counts and limits of the Spring application.

Key Components

  • Spring Cloud Gateway: The library that provides a simple, yet powerful way to route API requests of the application.
  • Redis: An in-memory data structure store that can be used as the database, cache, and message broker.
  • KeyResolver: The component that resolves the key for the rate-limiting and is typically based on the client IP or user identity.

Prerequisites:

  • Good understanding of the Spring Boot and Spring Reactive.
  • Basic understanding of the Rate Limiting concept.
  • JDK and IntelliJ Idea setup installed in your local system.
  • Maven for building dependency management.

Implementation of Rate Limiting in Spring WebFlux

Step 1: Create a Spring Project

Create a new Spring Boot project using spring Initializr and add the below dependencies.

Dependencies:

  • Spring Web Reactive
  • Lombok
  • Spring DevTools

After creating the project, the folder structure will be like below image in the IDE.

example-ratelimit Folder Structure


Step 2: Configure the Application Properties

Now, configure the application property of the project by the adding the below properties:

spring.application.name=example-ratelimit
server.port=8081


Step 3: Create the GreetController class

Java
package org.example.exampleratelimit;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@RestController // Annotation to define this class as a RESTful web service controller
@RequestMapping("/greet") // Maps HTTP requests to /greet to methods in this controller
public class GreetingController {

    @GetMapping // Annotation to map HTTP GET requests to this method
    public Mono<String> greet(ServerWebExchange exchange) {
        // Create a Mono that emits a greeting message
        return Mono.just("Hello, Welcome to Spring WebFlux with Rate Limiting!")
                .doOnSuccess(message -> {
                    // Add custom headers to the response for rate limiting information
                    exchange.getResponse().getHeaders().add("X-RateLimit-Limit", "10"); // Maximum allowed requests
                    exchange.getResponse().getHeaders().add("X-RateLimit-Remaining", ""); // Remaining requests
                    exchange.getResponse().getHeaders().add("X-RateLimit-Reset", ""); // Time until the rate limit resets
                });
    }
}

This code defines a Spring WebFlux REST controller that handles HTTP GET requests to the /greet endpoint. It returns a greeting message and adds custom headers to the response for rate limiting information, such as the request limit, remaining requests, and the reset time.

Step 4: Main class

No changes are required in the main class.

Java
package org.example.exampleratelimit;

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

@SpringBootApplication
public class ExampleRatelimitApplication {

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

}


pom.xml file:

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.3.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>example-ratelimit</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>example-ratelimit</name>
    <description>example-ratelimit</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


Step 5: Run the application

Now, run the application and it will start at port 8081 of Netty server.

example-ratelimit Application Runs


Setup the Gateway Service

Step 1: Create the Spring Project

Create a new Spring Boot project using spring Initializr and add the below dependencies.

Dependencies:

  • Spring Web Reactive
  • Lombok
  • Spring DevTools
  • Spring Cloud Gateway

After creating the project, the folder structure will be like below image:

ratelimit-gateway Folder Structure


Step 2: Configure the Application Properties

spring:
  cloud:
    gateway:
      routes:
        - id: greeting_route
          uri: http://localhost:8081
          predicates:
            - Path=/greet
          filters:
            - name: RequestRateLimiter
              args:
                key-resolver: "#{@userKeyResolver}"
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1
  data:
    redis:
      port: 6379
      host: localhost


Step 3: Create the RateLimitingConfig Class

Java
package org.example.ratelimitgateway;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

@Configuration // Indicates that this class contains Spring configuration
public class RateLimitingConfig {

    @Bean // Defines a bean for the application context
    public KeyResolver userKeyResolver() {
        // Resolves the key for rate limiting based on the client's IP address
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}

This code defines a configuration class for rate limiting in a Spring Cloud Gateway application. It includes a KeyResolver bean that resolves the key for rate limiting based on the client's IP address. The resolved key is used to track and limit requests from individual clients.

Step 4: Main Class

No changes are required in the main class.

Java
package org.example.ratelimitgateway;

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

@SpringBootApplication
public class RatelimitGatewayApplication {

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

}


pom.xml file:

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.3.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>ratelimit-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ratelimit-gateway</name>
    <description>ratelimit-gateway</description>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.2</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</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-circuitbreaker-reactor-resilience4j</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-spring-boot2</artifactId>
            <version>1.7.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


Step 5: Run the application

Now run this and it will start at port 8080 in Netty server.

ratelimit-gateway Application Runs


Testing the Application

Note: When the rate limit is exceeded then the server responds with the 429 Too Many Requests status code.

GET http://localhost:8080/greet

Output:

greet API Testing

By using Spring Cloud Gateway and Redis, we can easily set up and configure the rate limiting based on the client IP addresses or other criteria.


Next Article

Similar Reads