Spring Boot WebFlux WebFilter添加响应耗时Header报错问题
问题根因
报错和功能不符合预期来自两个核心问题:
- 响应头写入时机错误:
doOnSuccess、doOnError、doFinally回调触发时,响应大概率已经完成提交,此时响应头对象会变为只读的ReadOnlyHttpHeaders,调用add方法就会抛出你看到的UnsupportedOperationException。 - 计时变量存在线程安全问题:
requestTime被定义为Filter类的成员变量,而WebFilter在Spring容器中默认是单例,并发请求会反复修改这个共享变量,最终算出的耗时会出现错乱。
修复方案
做两处调整即可:
- 将请求起始时间定义为
filter方法的局部变量,每个请求进入filter时都会生成独立的计时变量,天然不存在并发覆盖问题 - 不要在响应提交后的回调中直接写入响应头,通过
ServerHttpResponse提供的beforeCommit钩子注册头写入逻辑,该钩子会在响应头正式发送给客户端前执行,此时响应头仍处于可写状态。
基础实现代码如下:
@Component public class TMPFilter implements WebFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { long requestStartTime = System.nanoTime(); ServerHttpResponse response = exchange.getResponse(); // 注册响应提交前回调 response.beforeCommit(() -> { long costNano = System.nanoTime() - requestStartTime; response.getHeaders().add("server-timing", Long.toString(costNano)); return Mono.empty(); }); return chain.filter(exchange); } }
如果需要区分请求成功/异常场景返回不同的头字段,可以先在doOnSuccess、doOnError回调中把计算好的耗时存入ServerWebExchange的自定义属性中,最后在beforeCommit回调里统一读取属性写入响应头即可,示例:
@Component public class TMPFilter implements WebFilter { private static final String TIMING_ATTR = "serverTiming"; @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { long requestStartTime = System.nanoTime(); ServerHttpResponse response = exchange.getResponse(); response.beforeCommit(() -> { long costNano = exchange.getAttributeOrDefault(TIMING_ATTR, System.nanoTime() - requestStartTime); response.getHeaders().add("server-timing", Long.toString(costNano)); return Mono.empty(); }); return chain.filter(exchange) .doOnSuccess(v -> exchange.getAttributes().put(TIMING_ATTR, System.nanoTime() - requestStartTime)) .doOnError(e -> exchange.getAttributes().put(TIMING_ATTR, System.nanoTime() - requestStartTime)); } }
内容的提问来源于stack exchange,提问作者Junias Ngoyi
相关产品推荐
相关产品推荐

