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

Spring WebFlux网关FormData丢失及block()调用报错问题求助

解决思路

1. 禁用阻塞操作block()

WebFlux基于异步非阻塞模型,Reactor的NIO线程绝对禁止使用block()/blockFirst()/blockLast()这类阻塞方法,必须通过链式异步操作处理请求体:

// 错误写法(会触发阻塞报错)
// MultiValueMap<String, String> formData = exchange.getFormData().block();

// 正确异步处理方式
exchange.getFormData()
    .flatMap(formData -> {
        // 在此处处理FormData逻辑
        return Mono.just(formData);
    })
    // 需整合到过滤器的Mono<Void>返回值中,而非单独subscribe
    .then(chain.filter(exchange));

2. 缓存请求体解决FormData丢失问题

WebFlux的请求体是一次性消费的Flux<DataBuffer>流,若在过滤器中读取了请求体,必须重新包装成新请求放回ServerWebExchange,否则下游服务无法读取。针对包含二进制文件的multipart/form-data请求,需特殊处理:

步骤1:缓存原始请求体

先将请求体缓存到交换属性中,再重新生成可重复读取的流:

DataBufferUtils.join(exchange.getRequest().getBody())
    .flatMap(dataBuffer -> {
        // 缓存原始请求体
        exchange.getAttributes().put("cachedRequestBody", dataBuffer);
        // 生成可重复读取的请求体流
        Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
        // 包装新请求
        ServerHttpRequest newRequest = exchange.getRequest().mutate()
            .body(cachedFlux)
            .build();
        ServerWebExchange decoratedExchange = exchange.mutate().request(newRequest).build();
        
        return Mono.just(decoratedExchange);
    })

步骤2:处理Multipart类型请求

对于带二进制文件的multipart/form-data,需用getMultipartData()解析,而非getFormData()(后者仅处理普通表单字段),修改后需重新构造请求体:

.flatMap(decoratedExchange -> {
    if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(decoratedExchange.getRequest().getHeaders().getContentType())) {
        return decoratedExchange.getMultipartData()
            .flatMap(multipartData -> {
                // 修改Multipart数据,比如添加字段
                multipartData.add(routeData.getFieldName(), username);
                
                // 重新构建Multipart请求体
                MultipartBodyBuilder builder = new MultipartBodyBuilder();
                multipartData.forEach((key, parts) -> {
                    parts.forEach(part -> {
                        if (part instanceof FilePart) {
                            builder.part(key, ((FilePart) part).content());
                        } else {
                            builder.part(key, part);
                        }
                    });
                });
                
                // 生成新请求体并包装请求
                ServerHttpRequest originalRequest = decoratedExchange.getRequest();
                Flux<DataBuffer> newBody = BodyInserters.fromMultipartData(builder.build())
                    .insert(originalRequest, new BodyInserterContext());
                ServerHttpRequest updatedRequest = originalRequest.mutate().body(newBody).build();
                ServerWebExchange updatedExchange = decoratedExchange.mutate().request(updatedRequest).build();
                
                return chain.filter(updatedExchange);
            });
    } else {
        // 处理普通表单请求
        return decoratedExchange.getFormData()
            .flatMap(formData -> {
                formData.add(routeData.getFieldName(), username);
                return chain.filter(decoratedExchange);
            });
    }
});

3. 规范修改查询参数的方式

原addQueryParam方法可优化为通过ServerHttpRequestDecorator包装请求,避免直接修改交换属性:

private ServerHttpRequest addQueryParam(ServerHttpRequest request, String username) {
    URI updatedUri = UriComponentsBuilder.fromUri(request.getURI())
        .queryParam(routeData.getFieldName(), username)
        .build(true)
        .toUri();
    return request.mutate().uri(updatedUri).build();
}

在过滤器中调用时,直接包装请求即可:

ServerHttpRequest updatedRequest = addQueryParam(exchange.getRequest(), username);
ServerWebExchange updatedExchange = exchange.mutate().request(updatedRequest).build();
return chain.filter(updatedExchange);

4. 全局过滤器的正确返回值

GlobalFilter的filter方法必须返回Mono<Void>,所有异步操作需整合到该返回值中,确保流程同步:

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    String username = // 从上下文获取用户名的逻辑
    return DataBufferUtils.join(exchange.getRequest().getBody())
        .flatMap(dataBuffer -> {
            // 缓存并包装请求体
            exchange.getAttributes().put("cachedRequestBody", dataBuffer);
            Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
            ServerHttpRequest newRequest = exchange.getRequest().mutate().body(cachedFlux).build();
            ServerWebExchange decoratedExchange = exchange.mutate().request(newRequest).build();
            
            // 根据请求类型处理
            if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(decoratedExchange.getRequest().getHeaders().getContentType())) {
                return decoratedExchange.getMultipartData()
                    .flatMap(multipartData -> {
                        ServerHttpRequest updatedRequest = addQueryParam(decoratedExchange.getRequest(), username);
                        return chain.filter(decoratedExchange.mutate().request(updatedRequest).build());
                    });
            } else {
                return decoratedExchange.getFormData()
                    .flatMap(formData -> {
                        ServerHttpRequest updatedRequest = addQueryParam(decoratedExchange.getRequest(), username);
                        return chain.filter(decoratedExchange.mutate().request(updatedRequest).build());
                    });
            }
        })
        .onErrorResume(e -> chain.filter(exchange));
}

内容的提问来源于stack exchange,提问作者Ali Canlı

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 09:15:58