news 2026/4/19 9:33:41

踩坑:Gateway 请求体只能被消费一次?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
踩坑:Gateway 请求体只能被消费一次?
    • 为什么请求体只能读一次?
    • 那怎么解决?—— 把 body “缓存”起来
    • 注意事项 & 我们的踩坑点
    • 有没有更简单的办法?
    • 我的看法

这个问题我是在写一个日志记录功能时撞上的。当时想在 Spring Cloud Gateway 里加个全局过滤器,把所有进来的请求参数(尤其是 POST 的 JSON)打个日志,方便排查问题。结果发现——请求体读了一次之后,下游服务就收不到 body 了!

一开始我还以为是代码写错了,反复检查,后来才明白:Reactor + Netty 环境下,请求体(RequestBody)默认只能读一次。这不是 bug,是设计如此。

今天就聊聊这个“坑”,以及我们是怎么绕过去的。


为什么请求体只能读一次?

在传统的 Servlet 里,HttpServletRequest的输入流可以被多次读取(虽然也不推荐),因为 Tomcat 底层做了缓冲。但 Gateway 不一样。

Gateway 基于 WebFlux,底层用的是 Netty。Netty 为了高性能,不会把整个请求体缓存在内存里。它是一个字节流,像水管一样,数据流过去就没了。你用ServerHttpRequestgetBody()拿到的是一个Flux<DataBuffer>,本质上是个只能消费一次的流

一旦你在 Filter 里把它subscribecollect读完了,下游路由到微服务的时候,body 就空了。

我们的经验是:任何试图直接读取原始 body 的操作,都会导致后续服务拿不到数据

比如下面这段“看似正常”的代码:

@ComponentpublicclassLogGlobalFilterimplementsGlobalFilter{@OverridepublicMono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain){ServerHttpRequestrequest=exchange.getRequest();// 危险!这样读完,body 就没了returnDataBufferUtils.join(request.getBody()).flatMap(dataBuffer->{Stringbody=StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer()).toString();System.out.println("请求体: "+body);// 这时候 body 已经被消费掉了!returnchain.filter(exchange);});}}

跑起来你会发现:日志是打出来了,但下游服务收到的 POST 请求是空的,直接报错“缺少参数”。


那怎么解决?—— 把 body “缓存”起来

关键思路是:读一次 body,然后重新构造一个新的 ServerHttpRequest,把读到的内容“塞回去”

Spring 提供了ServerHttpRequestDecorator,可以让我们包装原始请求。

下面是我们最终能用的版本:

@ComponentpublicclassCacheBodyGlobalFilterimplementsGlobalFilter,Ordered{@OverridepublicMono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain){// 只处理有 body 的请求,比如 POST/PUTif(exchange.getRequest().getHeaders().getContentLength()>0){returnDataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer->{// 保留一份 byte 数组byte[]bytes=newbyte[data_BUFFER.readableByteCount()];dataBuffer.read(bytes);DataBufferUtils.release(dataBuffer);// 释放原始 buffer// 构造新的请求体NettyDataBufferFactorynettyDataBufferFactory=newNettyDataBufferFactory(ByteBufAllocator.DEFAULT);DataBufferbodyDataBuffer=nettyDataBufferFactory.allocateBuffer(bytes.length);bodyDataBuffer.write(bytes);// 重写 requestServerHttpRequestnewRequest=newServerHttpRequestDecorator(exchange.getRequest()){@OverridepublicFlux<DataBuffer>getBody(){returnFlux.just(bodyDataBuffer);}};// 把新请求放回 exchangeServerWebExchangenewExchange=exchange.mutate().request(newRequest).build();// 打印日志(或者做其他事)StringbodyStr=newString(bytes,StandardCharsets.UTF_8);System.out.println("缓存后的请求体: "+bodyStr);returnchain.filter(newExchange);});}returnchain.filter(exchange);}@OverridepublicintgetOrder(){return-100;// 尽量靠前,确保在其他逻辑前执行}}

这段代码的核心就是:

  1. DataBufferUtils.join()把流聚合成一个DataBuffer
  2. 转成byte[]保存下来。
  3. ServerHttpRequestDecorator重写getBody()方法,返回我们缓存的数据。
  4. exchange.mutate().request(...).build()替换掉原来的请求。

这样,后续的过滤器和下游服务拿到的还是完整的 body。


注意事项 & 我们的踩坑点

  • 别忘了 release 原始 DataBuffer!Netty 的内存管理很严格,不释放会导致内存泄漏。DataBufferUtils.release(dataBuffer)很关键。
  • 只对需要读 body 的请求做缓存。GET 请求没 body,没必要处理,还能省性能。
  • 大文件上传别这么干!如果有人 POST 一个 100MB 的文件,你全读进内存,服务直接 OOM。所以最好加个 body 大小限制,比如只缓存小于 1MB 的请求。
  • 编码问题:我们固定用了UTF-8,如果你的系统用别的编码,记得改。

有没有更简单的办法?

其实 Spring Cloud Gateway 官方也意识到这个问题了。如果你只是想打印日志,可以用现成的ModifyRequestBodyGatewayFilterFactory,它内部已经做了 body 缓存。

但如果你想在 Filter 里自己处理 body 内容(比如验签、解密、改字段),那就得手写上面那种逻辑。


我的看法

我认为,这个“只能读一次”的设计虽然反直觉,但其实是合理的。高性能网关不应该默认把整个请求体缓存起来,那样太浪费内存。要不要缓存,应该由业务决定

只是作为开发者,得清楚这个前提:在响应式流里,数据流是一次性的,想重复用,就得自己存一份

现在每次写 Gateway 的 Filter,我都会先问一句:“这里要读 body 吗?” 如果要,立马套上缓存模板,不敢偷懒。

希望这篇碎碎念能帮你少走点弯路。毕竟,谁也不想 debug 一整天,最后发现是 body 被吃掉了

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 17:02:26

Ring-1T开放下载:万亿参数AI模型解锁深度推理能力

近日&#xff0c;人工智能领域再添重磅突破——万亿参数规模的深度思考模型Ring-1T正式开放下载。这款由Bailing团队开发的开源大语言模型&#xff0c;凭借其在数学竞赛、代码生成和逻辑推理等复杂任务上的卓越表现&#xff0c;有望为科研机构和开发者社区提供强大的AI推理工具…

作者头像 李华
网站建设 2026/4/16 18:00:10

实时级半实物仿真测试平台 ETest_RT

1&#xff09;产品简介ETest_RT是一款高实时性嵌入式系统半实物仿真测试平台&#xff08;Embedded Real-Time Testing System Studio RT,简称&#xff1a;ETest_RT&#xff09;&#xff0c;仿真步长可达微秒级&#xff0c;适合于航空航天、武器装备、汽车电子、仪器仪表等领域的…

作者头像 李华
网站建设 2026/4/18 19:40:02

Qwen-Image-Edit-Rapid-AIO:4步实现快速AI图文编辑

导语 【免费下载链接】Qwen-Image-Edit-Rapid-AIO 项目地址: https://ai.gitcode.com/hf_mirrors/Phr00t/Qwen-Image-Edit-Rapid-AIO Qwen-Image-Edit-Rapid-AIO作为一款基于Qwen系列模型优化的AI图文编辑工具&#xff0c;通过模型融合与加速技术创新&#xff0c;将AI图…

作者头像 李华