Micronaut RateLimiter

Rate limiting support for Micronaut

Version:

1 Introduction

This project brings rate limiting support to Micronaut. The core module contains the common functionality and configuration. An implementation is required to have any rate limiting functionality.

To get started, you need to declare a dependency on one of the implementations. See the next section on installation.

To use a snapshot version of this library, check the documentation to use snapshots.

2 Installation

To start using rate limiters in Micronaut, simply express a dependency on one of the implementations that are available. See the documentation for your desired implementation for details.

3 Configuration

The core library provides configuration that applies to every implementation. By default when an implementation is on the classpath and enabled, rate limiting will apply to all APIs. You can limit the functionality to a subset of your controllers and methods through the following configuration.

micronaut:
  ratelimiting:
    paths: (1)
      - /api/**
1 One or more Ant style paths can be supplied to denote what parts of your application should be rate limited.
The presence of the @RateLimit annotation does not automatically allow an API to be being rate limited. All APIs must be in one of the paths supplied via configuration.

4 Getting Started

Before you can get started controlling rate limits, there is one concept to understand and potentially an implementation to write. Rate limits are typically broken up into groups or "buckets". For example a rate limit may apply per user, or per organization, etc. There is no clear common default way of grouping the users of an API so that implementation is up to you. An interface BucketNameResolver is used to allow for custom bucket name resolution logic.

We have provided a simple implementation that rate limits by IP address. That implementation is disabled by default. To turn it on, set micronaut.ratelimiting.ip-address-resolver: true in your configuration. This implementation is mostly to make getting started with rate limiting easier.

Once an implementation of BucketNameResolver, by default all requests will be rate limited using the defaults set by the rate limiting implementation.

5 Usage

Rate limiting will apply to any paths that have been configured (by default all paths). You can control the rate limiter configuration for any given controller or method as well as exclude any controller or method from being rate limited.

Rate Limit Configurations

Each implementation will provide a way to supply rate limiting configurations. To use a given configuration for a section of your API, simply add the @RateLimit annotation to the controller or method and supply the configuration name. For example:

package example;

import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.ratelimiter.annotation.NoRateLimit;
import io.micronaut.ratelimiter.annotation.RateLimit;

@RateLimit("high") (1)
@Controller("/api") (2)
public class RateLimitController {

    @NoRateLimit (3)
    HttpResponse<?> someMethod() {
        return HttpResponse.ok();
    }
}
package example

import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.ratelimiter.annotation.NoRateLimit
import io.micronaut.ratelimiter.annotation.RateLimit

@RateLimit("high") (1)
@Controller("/api") (2)
class RateLimitController {

    @NoRateLimit (3)
    HttpResponse<?> someMethod() {
        HttpResponse.ok()
    }
}
package example

import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.ratelimiter.annotation.NoRateLimit
import io.micronaut.ratelimiter.annotation.RateLimit

@RateLimit("high") (1)
@Controller("/api") (2)
class RateLimitController {

    @NoRateLimit (3)
    fun someMethod() = HttpResponse.ok<Any>()
}
1 The annotation is controlling which configuration will apply to this controller. The annotation can also be applied to individual methods for finer control
2 The controllers path matches one of the configured paths
3 The @NoRateLimit annotation is being used to denote this controller should not be rate limited, even though its path does match one of the configured paths

The @NoRateLimit can also be used at the controller level:

package example;

import io.micronaut.http.annotation.Controller;
import io.micronaut.ratelimiter.annotation.NoRateLimit;

@NoRateLimit
@Controller("/api")
public class NoRateLimitController {
}
package example

import io.micronaut.http.annotation.Controller
import io.micronaut.ratelimiter.annotation.NoRateLimit

@NoRateLimit
@Controller("/api")
class NoRateLimitController {
}
package example

import io.micronaut.http.annotation.Controller
import io.micronaut.ratelimiter.annotation.NoRateLimit

@NoRateLimit
@Controller("/api")
class NoRateLimitController {
}

6 Resilience4j

An implementation of rate limiting for Micronaut has been created using Resilience4j.

To get started, simply express a dependency on this module:

runtime("io.micronaut.ratelimiter:micronaut-ratelimiter-resilience4j")
<dependency>
    <groupId>io.micronaut.ratelimiter</groupId>
    <artifactId>micronaut-ratelimiter-resilience4j</artifactId>
    <scope>runtime</scope>
</dependency>

6.1 Configuration

Resilience4j only has support for a limited number of options, and those options are configurable through Micronaut configuration. Each grouping of options is referred to as a "configuration" and those configurations can be referenced in your code through the @RateLimiter annotation.

If the annotation is missing, then the default configuration will be applied. The default configuration can be supplied through the special name of "default" in your configuration. If that is missing then the defaults provided by the Resilience4j library will be used. For example:

resilience4j:
  ratelimiter:
    configurations:
      default: (1)
        timeout: 100ms
      low: (2)
        period: 10s
        limit: 1
      high: (3)
        period: 1s
        limit: 100
1 The default configuration that will be used if no configuration is set via the annotation. Note that the missing values will inherit the defaults from Resilience4j.
2 A configuration called "low". This configuration only allows a single request every 10 seconds.
3 A configuration called "high". This configuration allows for 100 requests every second.
The "low" and "high" configurations do not inherit from the default. The default configuration only applies when there is no specified configuration for a rate limited API.

For a full reference of the resilience4j options, see the See configuration reference.

6.2 Error Response

When the Resilience4j rate limiter rejects a request, a RequestNotPermitted exception will be emitted. This exception can be handled just like any other in Micronaut and a default implementation of ExceptionHandler comes with this library to turn the exception into the standard 429 response.

If the default implementation is not sufficient, you may override it by replacing RequestNotPermittedHandler with your own implementation.