Spring Boot限流场景:响应提交后如何修改响应状态与响应体?
解决Filter限流器中捕获响应状态码并返回429的问题
核心思路是用自定义响应包装器捕获后端处理后的响应状态码,同时调整限流判断的执行顺序——先判断是否触发限流,再放行请求并根据响应结果更新限流计数。
1. 自定义响应包装器捕获状态码
需要覆盖所有可能设置响应状态码的方法,确保能准确捕获后端返回的状态:
public class StatusCapturingResponseWrapper extends HttpServletResponseWrapper { private int statusCode; public StatusCapturingResponseWrapper(HttpServletResponse response) { super(response); } @Override public void setStatus(int sc) { super.setStatus(sc); this.statusCode = sc; } @Override public void sendError(int sc) throws IOException { super.sendError(sc); this.statusCode = sc; } @Override public void sendError(int sc, String msg) throws IOException { super.sendError(sc, msg); this.statusCode = sc; } @Override public void setStatus(int sc, String sm) { super.setStatus(sc, sm); this.statusCode = sc; } public int getCapturedStatusCode() { // 未设置状态码时默认返回200 return statusCode != 0 ? statusCode : HttpServletResponse.SC_OK; } }
2. 实现限流器Filter(基于OncePerRequestFilter)
调整逻辑顺序:先判断是否触发限流,触发则直接返回429;未触发则放行请求,事后根据响应状态码更新限流计数(仅失败请求计入限流):
public class RateLimitFilter extends OncePerRequestFilter { // 示例:本地计数器,实际生产建议用Redis/Guava RateLimiter实现分布式限流 private final AtomicInteger failRequestCount = new AtomicInteger(0); // 限流阈值:失败请求达到10次后触发限流 private static final int LIMIT_THRESHOLD = 10; // 定义成功请求的状态码(可根据需求调整,比如包含201、204等) private static final int SUCCESS_STATUS = HttpServletResponse.SC_OK; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 先判断是否触发限流:达到阈值直接返回429 if (failRequestCount.get() >= LIMIT_THRESHOLD) { response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":429,\"msg\":\"Too many failed requests, please try again later\"}"); return; } // 包装响应,准备捕获状态码 StatusCapturingResponseWrapper responseWrapper = new StatusCapturingResponseWrapper(response); // 放行请求,让后端业务逻辑处理 filterChain.doFilter(request, responseWrapper); // 获取后端返回的状态码,仅失败请求计入限流计数 int statusCode = responseWrapper.getCapturedStatusCode(); if (statusCode != SUCCESS_STATUS) { failRequestCount.incrementAndGet(); } } }
关键注意事项
- 必须覆盖所有设置状态码的方法:后端可能用
setStatus、sendError等不同方法设置状态,包装器要全部捕获才能拿到正确的状态码 - 限流判断要前置:如果在
chain.doFilter之后再判断限流,此时响应已经提交,无法再修改状态码返回429 - 异步场景适配:如果你的接口用了异步处理(
AsyncContext),需要额外处理异步完成后的状态捕获逻辑,同步接口直接用上述方案即可
内容的提问来源于stack exchange,提问作者Euxinos
相关产品推荐
相关产品推荐

