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

Spring Cloud Gateway如何正确提取JSON请求体实现自定义处理

Spring Cloud Gateway 提取JSON请求体实现方案

Spring Cloud Gateway基于WebFlux响应式架构构建,请求体属于单次读取的字节流,直接读取后不做缓存会导致下游微服务无法获取请求数据,这是该场景下最常见的踩坑点。你之前调研的RequestBodyRewrite类实现属于请求/响应体重写组件,适合需要修改Body内容再转发的场景,你的需求仅需读取Body做逻辑处理后追加请求头,不需要引入重写逻辑的额外开销。

推荐方案:自定义全局过滤器实现(适配你的业务场景)

这种方案不需要滥用路由断言承载业务逻辑,可统一作用于所有匹配路由,性能和可维护性都更优,核心逻辑是借助官方提供的缓存工具解决请求流单次读取的问题,再直接反序列化JSON内容:

  • 实现GlobalFilter和Ordered接口,设置过滤器优先级高于路由转发的NettyRoutingFilter
  • 用ServerWebExchangeUtils.cacheRequestBodyAndRequest方法缓存请求体,避免流读取后下游无法获取
  • 直接将缓存的请求体反序列化为目标Java对象或通用JSON节点,执行自定义逻辑后把处理结果写入请求头传递给下游

可直接参考的实现代码:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

@Component
public class JsonBodyExtractFilter implements GlobalFilter, Ordered {

    private final ObjectMapper objectMapper;

    // 注入Spring容器默认的ObjectMapper即可,不需要额外实例化
    public JsonBodyExtractFilter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 仅拦截JSON类型请求,减少非必要性能开销
        String contentType = exchange.getRequest().getHeaders().getFirst("Content-Type");
        if (contentType == null || !contentType.contains("application/json")) {
            return chain.filter(exchange);
        }

        return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, cachedRequest -> {
            ServerRequest serverRequest = ServerRequest.create(
                    exchange.mutate().request(cachedRequest).build(),
                    HandlerStrategies.withDefaults().messageReaders()
            );
            // 可替换为你自定义的SomeClass,直接反序列化为业务实体
            return serverRequest.bodyToMono(JsonNode.class)
                    .flatMap(jsonBody -> {
                        // 此处编写自定义业务逻辑
                        String processedResult = jsonBody.get("targetField").asText();
                        // 追加处理后的数据到请求头,做URL编码避免特殊字符导致头解析异常
                        ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
                                .header("X-Biz-Processed-Result", 
                                        URLEncoder.encode(processedResult, StandardCharsets.UTF_8))
                                .build();
                        // 传递修改后的请求对象到下游
                        return chain.filter(exchange.mutate().request(mutatedRequest).build());
                    });
        });
    }

    @Override
    public int getOrder() {
        // 优先级设置为-1,确保在默认顺序为Integer.MAX_VALUE的路由转发过滤器前执行
        return -1;
    }
}

手动解析DataBuffer格式请求体的方法

如果你在其他场景下拿到了DataBuffer类型的请求体返回值,不要直接转字符串,需要正确处理内存释放和流回写问题,解析逻辑参考:

// 注意:这种方式读取后必须重新把请求体写回exchange,否则下游拿不到数据
return DataBufferUtils.join(exchange.getRequest().getBody())
        .flatMap(dataBuffer -> {
            byte[] bodyBytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bodyBytes);
            // 必须手动释放DataBuffer,否则会造成内存泄漏
            DataBufferUtils.release(dataBuffer);
            String jsonStr = new String(bodyBytes, StandardCharsets.UTF_8);
            try {
                JsonNode jsonNode = objectMapper.readTree(jsonStr);
                // 执行自定义业务逻辑
            } catch (JsonProcessingException e) {
                return Mono.error(e);
            }
            // 重新包装请求体回写后再传递给下游
            return chain.filter(exchange);
        });

不推荐使用readBody断言承载业务逻辑的原因

readBody本身是路由断言组件,设计目标是判断请求是否匹配路由规则,而非承载业务处理逻辑:

  • 每个需要读取Body的路由都要单独配置断言,配置冗余,维护成本高
  • 断言逻辑里做业务处理会破坏路由配置的职责边界,后续排查问题难度大
  • 断言中获取的cachedRequestBodyObject本质也是框架缓存的请求体对象,全局过滤器方式可以实现完全相同的能力,且灵活性更高

注意:请求头存在长度限制(多数Web服务器默认限制为8KB~16KB),如果处理后的数据量较大,不要直接放到请求头中传递,可以将处理结果存入Redis等缓存,把对应的缓存Key放到请求头传给下游微服务即可。

内容的提问来源于stack exchange,提问作者Akhil Korissery

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 16:27:25