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
相关产品推荐
相关产品推荐

