You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 12:49:51