Spring Cloud Gateway过滤器中读取请求体后,响应未返回给客户端(响应体为空)
Spring Cloud Gateway过滤器中读取请求体后,响应未返回给客户端(响应体为空)
我之前做Spring Cloud Gateway验签过滤器的时候,也踩过一模一样的坑!用block()同步读取请求体的时候一切正常,换成全响应式非阻塞写法后,明明下游服务日志显示已经返回了响应,但客户端就是收不到响应体,一度怀疑是网关把响应给吞了。
问题根源
核心原因是WebFlux的请求体是一次性消费的Flux<DataBuffer>——当你在过滤器里直接读取了请求体的数据流,如果没有重新生成一个可重复读取的请求体数据流传递给下游,后续的请求处理链(包括下游服务调用)虽然看起来执行了,但实际上可能因为请求体已被消耗导致流程异常,或者更关键的是:响应式链没有正确衔接,导致网关没有把下游的响应体继续传递回客户端。
正确的响应式实现方式
下面是我经过无数次调试后整理的正确写法,核心是缓存请求体+重新包装请求+正确串联响应式链:
首先,在你的GatewayFilter中实现逻辑:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 缓存请求体:将Flux<DataBuffer>转换为Mono<byte[]>,方便后续验签 Mono<byte[]> requestBodyMono = exchange.getRequest().getBody() .map(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); // 手动释放DataBuffer,避免内存泄漏 return bytes; }) .reduce(new byte[0], (mergedBytes, newBytes) -> { // 合并分片的字节数组 byte[] result = new byte[mergedBytes.length + newBytes.length]; System.arraycopy(mergedBytes, 0, result, 0, mergedBytes.length); System.arraycopy(newBytes, 0, result, mergedBytes.length, newBytes.length); return result; }); // 2. 用响应式方式处理验签,串联整个过滤链 return requestBodyMono.flatMap(requestBody -> { // 调用响应式的Repository获取验签密钥(这里替换成你的验签前置逻辑) return signatureRepository.findValidKey() .flatMap(validKey -> { // 执行验签逻辑 boolean isSignatureValid = validateRequestSignature(requestBody, validKey); if (!isSignatureValid) { // 验签失败,直接返回403 exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); return exchange.getResponse().setComplete(); } // 3. 重新包装请求体,让下游服务可以正常读取 ServerHttpRequest decoratedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { // 把缓存的字节数组重新转换成Flux<DataBuffer> return Flux.just(exchange.getResponse().bufferFactory().wrap(requestBody)); } }; // 4. 传递包装后的请求,继续执行过滤链 return chain.filter(exchange.mutate().request(decoratedRequest).build()); }); }); } // 替换成你的实际验签方法 private boolean validateRequestSignature(byte[] requestBody, String validKey) { // 这里写你的验签逻辑,比如用RSA、HMAC等校验请求体签名 return true; }
关键注意事项
- 一定要手动释放DataBuffer:WebFlux的DataBuffer是池化的,如果不手动释放会导致内存泄漏,所以在读取完每个DataBuffer后必须调用
DataBufferUtils.release(dataBuffer)。 - 必须重新包装请求:用
ServerHttpRequestDecorator重写getBody()方法,返回我们缓存的请求体数据流,这样下游服务才能正确读取请求体完成业务逻辑。 - 响应式链不能断:整个过滤器的返回值必须是串联了验签逻辑和
chain.filter()的Mono<Void>,不能单独返回验签的Mono而忽略了过滤链的调用,否则下游服务不会被触发,或者响应无法传递回客户端。
为什么阻塞式写法能工作?
用block()的时候其实是强行把响应式流同步化了,这时候Spring Cloud Gateway的内部机制可能帮你处理了请求体的重复读取(或者你的验签逻辑同步完成后,请求体的数据流还没被完全消费),但这种写法会破坏响应式编程的非阻塞特性,在高并发场景下会导致线程阻塞,性能急剧下降,所以非常不推荐。
内容来源于stack exchange
相关产品推荐
相关产品推荐

