Enterprise rate limiter for spring web apps, based on rate-limiter-web-core.
We believe that rate limiting should be as simple as:
@Rate("10/s") // 10 permits per second for all methods in this class
@Controller
@RequestMapping("/api/v1")
public class GreetingResource {
@Rate(permits=10, when="web.request.user.role = GUEST")
@GetMapping("/smile")
public String smile() {
return ":)";
}
@Rate(permits=1, when="jvm.memory.available < 1gb")
@GetMapping("/greet")
public String greet(@RequestParam("who") String who) {
return "Hello " + who;
}
}Please first read the rate-limiter-web-core documentation.
To add a dependency on rate-limiter-spring using Maven, use the following:
<dependency>
<groupId>io.github.poshjosh</groupId>
<artifactId>rate-limiter-spring</artifactId>
<version>0.8.0</version>
</dependency>Note: Spring boot usage is in the next section.
1. Implement RateLimitProperties
@Component
public class RateLimitPropertiesImpl implements RateLimitProperties {
// If not using annotations, return an empty list
@Override
public List<String> getResourcePackages() {
return Collections.singletonList("com.myapp.web.rest");
}
// If not using properties, return an empty map
@Override
public List<Rates> getRates() {
// Accept only 2 tasks per second
return Collections.singletonList(Rates.of("task_queue", Rate.ofSeconds(2)));
}
}2. Extend RateLimitingFilter
@Component
public class RateLimitingFilterImpl extends RateLimitingFilter {
public RateLimitingFilterImpl(RateLimitProperties properties) {
super(properties);
}
@Override
protected void onLimitExceeded(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) throws IOException {
response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(),
HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
}
}At this point, your application is ready to enjoy the benefits of rate limiting.
3. Annotate classes and/or methods.
package com.myapp.web.rest;
import io.github.poshjosh.ratelimiter.model.Rate;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController @RequestMapping("/my-resources") public class MyResource {
// Only 25 calls per second for users in role GUEST
@Rate(permits = 25, when = "web.request.user.role = GUEST") @GetMapping("/greet/{name}") public ResponseEntity<String> greet(
@PathVariable String name) {
return ResponseEntity.ok("Hello " + name);
}
}1. Configure your spring application
@SpringBootApplication
@EnableConfigurationProperties(MyApp.MyRateLimitProperties.class)
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
@ConfigurationProperties(prefix = "rate-limiter", ignoreUnknownFields = false)
public class MyRateLimitProperties extends RateLimitPropertiesSpring { }
@Component
public static class MyAppFilter extends RateLimitingFilter {
public MyAppFilter(RateLimitProperties properties) {
super(properties);
}
@Override
protected void onLimitExceeded(
HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
response.sendError(429, "Too many requests");
}
}
}2. Add required rate-limiter properties
Specify either resource-packages or resource-classes
rate-limiter:
resource-packages: com.myapp.web.rest
#resource-classes: com.myapp.web.rest.MyResourceAt this point your application is ready to enjoy the benefits of rate limiting
3. Annotate classes and/or methods.
Annotate classes and/or methods as described previously.
4. (Optional) Add more rate-limit properties
rate-limiter:
resource-packages: com.myapp.web.rest
rates:
# Cap streaming of video to 5kb per second
- id: video_download
permits: 5000
duration: PT1S
# Limit requests to this resource to 10 per minute
- id: com.myapp.web.rest.MyResource
permits: 10
duration: PT1M
# Accept only 2 tasks per second
- id: task_queue
permits: 2
duration: PT1SConfigure rate limiting as described in the rate-limiter-web-core documentation.
When you configure rate limiting using properties, you could:
-
Rate limit a class from properties by using the class ID.
-
Rate limit a method from properties by using the method ID.
public class RateLimitPropertiesImpl implements RateLimitProperties {
@Override
public List<Rates> getRates() {
List<Rates> ratesList = new ArrayList<>();
// Rate limit a class
String classId = RateId.of(MyResource.class);
ratesList.add(Rates.of(classId, Rate.ofMinutes(10)));
// Rate limit a method
String methodId = RateId.of(MyResource.class.getMethod("greet", String.class));
ratesList.add(Rates.of(methodId, Rate.ofMinutes(10)));
return ratesList;
}
}The expression language allows us to write expressive rate conditions, e.g:
@RateCondition("web.request.user.role = GUEST")
@RateCondition("jvm.memory.free < 1GB")
| format | example | description |
|---|---|---|
| LHS = RHS | web.request.header[X-RateLimit-Limit] != | true, when the X-RateLimit-Limit header exists |
| LHS[key] = val | web.request.parameter[limited] = true | true, when request parameter limited equals true |
| LHS = [A ⎢ B] | web.request.user.role = [GUEST ⎢ RESTRICTED] | true, when the user role is either GUEST or RESTRICTED |
| LHS[key] = [A ⎢ B] | web.request.cookie[name] = [val_0 ⎢ val_1] | true, when cookie named name is either val_0 or val_1 |
| LHS[key] = [A & B] | web.request.header[name] = [val_0 & val_1] | true, when header named name has both val_0 and val_1 as values |
A rich set of conditions may be expressed as detailed in the web specification.
Usually, you are provided with appropriate RateLimiters based on the annotations
and properties you specify. However, you could manually create and use RateLimiters.
class MyResource {
RateLimiter rateLimiter = RateLimiterRegistry.of(MyResource.class, "smile");
@Rate(name = "smile", permits = 2)
String smile() {
return ":)";
}
}This way you use the RateLimiter as you see fit.
Please read the annotation specs. It is concise.
Enjoy! 😉