Implement Multi‑Dimensional Bandwidth Throttling in Spring Boot 3
This article presents a complete Spring Boot 3 solution for multi‑dimensional network bandwidth throttling using a manually implemented token‑bucket algorithm, custom HandlerInterceptor, HttpServletResponseWrapper, and RateLimitedOutputStream, with detailed code samples, configuration options, and performance tuning guidance.
Overview
The solution implements multi‑dimensional network bandwidth throttling in Spring Boot 3. Core throttling logic uses a token‑bucket algorithm; request interception, response wrapping, and output‑stream control are handled by HandlerInterceptor, HttpServletResponseWrapper, and a custom RateLimitedOutputStream.
Why Bandwidth Throttling Is Needed
Bandwidth throttling limits data transfer speed (e.g., 200 KB/s) instead of request count. Typical scenarios:
File‑download services : free users 200 KB/s, VIP users 2 MB/s.
Video streaming : 480P → 500 KB/s, 1080P → 3 MB/s.
API protection : large‑payload endpoints (e.g., report export) could otherwise consume the entire outbound bandwidth.
Core Principle: Token‑Bucket Algorithm
Tokens are added to a bucket at a fixed refill rate; each data chunk consumes tokens. Key parameters:
Capacity : maximum burst size (e.g., 200 KB).
Refill Rate : long‑term average speed (e.g., 200 KB/s).
Chunk Size : size of each write operation, affecting smoothness (e.g., split 8 KB into four 2 KB writes).
Sending data – preparation: Calculate elapsed time since the last refill. Compute new tokens as elapsedTime × refillRate . Update bucket token count, capped by capacity. Sending data – transmission: Check if enough tokens are available. If sufficient, deduct tokens and send data. If insufficient, compute wait time (missingTokens / refillRate) , sleep precisely, then consume tokens.
Technical Design
Overall Flow
请求流程:
┌─────────────────────────────────────┐
│ 1. DispatcherServlet 分发请求 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. BandwidthLimitInterceptor.preHandle()
│ - 解析 @BandwidthLimit 注解
│ - 从 BandwidthLimitManager 获取共享 TokenBucket
│ - 创建 BandwidthLimitResponseWrapper 并存入 request attribute
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. Controller 处理请求
│ - 通过 BandwidthLimitHelper.getLimitedResponse() 获取包装后的响应
│ - 向响应流写入数据(自动触发限速)
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4. BandwidthLimitInterceptor.afterCompletion()
│ - 清理资源,关闭流
└─────────────────────────────────────┘Why Choose HandlerInterceptor
Spring MVC provides Filter (executed before DispatcherServlet) and HandlerInterceptor (executed after handler resolution). Annotation parsing requires the HandlerMethod object, which is unavailable in a Filter. Therefore the solution uses HandlerInterceptor to access method‑level annotations.
Core Component Responsibilities
@BandwidthLimit: declarative annotation for throttling parameters. BandwidthLimitInterceptor: intercepts requests, parses the annotation, creates the response wrapper. BandwidthLimitManager: manages token buckets for GLOBAL, API, USER, and IP dimensions. BandwidthLimitResponseWrapper: extends HttpServletResponseWrapper and overrides getOutputStream() to return a custom rate‑limited stream. RateLimitedOutputStream: implements throttling by wrapping a shared TokenBucket. TokenBucket: concrete token‑bucket implementation. BandwidthLimitHelper: retrieves the wrapped response from request attributes.
Multi‑Dimensional Throttling Implementation
Global Limiting (GLOBAL)
All requests share a single bucket, protecting overall server egress. Example limits total bandwidth to 10 MB/s regardless of concurrent downloads.
@BandwidthLimit(value = 200, unit = BandwidthUnit.KB, type = LimitType.GLOBAL)
@GetMapping("/download/global")
public void downloadGlobal(HttpServletResponse response) throws IOException {
HttpServletResponse limitedResponse = BandwidthLimitHelper.getLimitedResponse(request, response);
// write data …
}API‑Level Limiting (API)
Each endpoint has an independent bucket, so traffic on one API does not affect another.
@BandwidthLimit(value = 500, unit = BandwidthUnit.KB, type = LimitType.API)
@GetMapping("/download/file")
public void downloadFile(HttpServletResponse response) throws IOException {
// file download logic
}
@BandwidthLimit(value = 2048, unit = BandwidthUnit.KB, type = LimitType.API)
@GetMapping("/stream/video")
public void streamVideo(HttpServletResponse response) throws IOException {
// video stream logic
}User‑Level Limiting (USER)
Limits are applied per user identifier (e.g., request header X-User-Id). Different limits can be configured for free and VIP users.
@BandwidthLimit(value = 200, unit = BandwidthUnit.KB, type = LimitType.USER, free = 200, vip = 2048)
@GetMapping("/download/user")
public void downloadByUser(@RequestHeader("X-User-Type") String userType,
HttpServletResponse response) throws IOException {
// automatically applies 200KB/s or 2MB/s based on user type
}IP‑Level Limiting (IP)
Limits are applied per client IP, with support for proxy headers X-Forwarded-For and X-Real-IP.
@BandwidthLimit(value = 300, unit = BandwidthUnit.KB, type = LimitType.IP)
@GetMapping("/download/ip")
public void downloadByIp(HttpServletResponse response) throws IOException {
// each distinct IP limited to 300KB/s
}Parameter Tuning Guide
Bucket Capacity Selection
Rate × 0.5 : smooth flow, no burst – suitable for strict control.
Rate × 1.0 : allows a 1‑second burst – recommended default.
Rate × 2.0 : allows a 2‑second burst – useful for fast first‑screen loading.
@BandwidthLimit(value = 200, unit = BandwidthUnit.KB, capacityMultiplier = 1.0)Chunk Size Selection
Chunk size influences smoothness. Empirical formula: chunkSize = bandwidth / 50. Recommended values:
200 KB/s → 1‑4 KB
1 MB/s → 4‑8 KB
5 MB/s+ → 8‑16 KB
// automatic calculation (recommended)
@BandwidthLimit(value = 200, unit = BandwidthUnit.KB, chunkSize = -1)
// manual specification
@BandwidthLimit(value = 200, unit = BandwidthUnit.KB, chunkSize = 4096)Key Code Implementation
1. Token‑Bucket Core Algorithm
The bucket uses System.nanoTime() for nanosecond‑level timestamps, ensuring precise rate control.
public synchronized void acquire(long permits) {
// 1. Refill tokens
refill();
// 2. If enough tokens, consume immediately
if (tokens >= permits) {
tokens -= permits;
return;
}
// 3. Calculate deficit and wait time
long deficit = permits - tokens;
long waitNanos = (deficit * 1_000_000_000L) / refillRate;
// 4. Precise sleep
sleepNanos(waitNanos);
// 5. After waiting, consume tokens
tokens = 0;
}
private void refill() {
long now = System.nanoTime();
long elapsedNanos = now - lastRefillTime;
long newTokens = (elapsedNanos * refillRate) / 1_000_000_000L;
tokens = Math.min(capacity, tokens + newTokens);
lastRefillTime = now;
}2. Response Wrapper
HttpServletResponseWrapperoverrides getOutputStream() to return a RateLimitedOutputStream that uses the shared TokenBucket.
public class BandwidthLimitResponseWrapper extends HttpServletResponseWrapper {
private final TokenBucket sharedTokenBucket;
private RateLimitedOutputStream limitedOutputStream;
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (limitedOutputStream == null && sharedTokenBucket != null) {
limitedOutputStream = new RateLimitedOutputStream(
super.getOutputStream(),
sharedTokenBucket,
bandwidthBytesPerSecond
);
}
return limitedOutputStream;
}
}3. Interceptor Creating the Wrapper
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
BandwidthLimit annotation = findAnnotation(handler);
if (annotation != null) {
TokenBucket bucket = limitManager.getBucket(
annotation.type(),
annotation.key(),
annotation.capacity(),
annotation.rate()
);
BandwidthLimitResponseWrapper wrappedResponse = new BandwidthLimitResponseWrapper(
response, bucket, annotation.bandwidthBytesPerSecond(), annotation.chunkSize()
);
request.setAttribute("BandwidthLimitWrappedResponse", wrappedResponse);
}
return true;
}4. Controller Retrieving the Wrapped Response
@GetMapping("/download/global")
public void downloadGlobal(HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpServletResponse limitedResponse = BandwidthLimitHelper.getLimitedResponse(request, response);
limitedResponse.setContentType("application/octet-stream");
limitedResponse.setHeader("Content-Disposition", "attachment; filename=test.bin");
// write data – throttling applied automatically
limitedResponse.getOutputStream().write(data);
}Conclusion
The implementation combines the token‑bucket algorithm with HandlerInterceptor and HttpServletResponseWrapper to provide real‑time, multi‑dimensional bandwidth throttling in Spring Boot. It supports GLOBAL, API, USER, and IP dimensions and can be applied to file download, video streaming, and API protection scenarios.
Project Code
https://github.com/yuboon/java-examples/tree/master/springboot-netspeed-limit
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
