Spring Gateway如何针对4xx/5xx响应条件化修改响应体?
解决方案
针对你的需求,我们可以通过实现带条件判断的GlobalFilter来精准处理4xx/5xx错误响应,同时避免非JSON类型Payload的解析失败问题。
核心思路
- 状态码过滤:仅对4xx/5xx错误状态码的响应进行处理
- Content-Type校验:只处理
application/json或兼容JSON类型的响应体 - 容错处理:JSON解析失败时直接返回原始响应,避免服务异常
- 保持状态码:修改响应体的同时保留原始错误状态码
完整代码实现
import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Component public class ErrorResponseFilter implements GlobalFilter, Ordered { private final ObjectMapper objectMapper; // 注入Spring自动配置的ObjectMapper public ErrorResponseFilter(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) { ServerHttpResponse originalResponse = exchange.getResponse(); // 仅处理错误状态码的响应 if (!HttpStatus.isError(originalResponse.getStatusCode())) { return chain.filter(exchange); } // 包装响应对象,用于修改响应体 ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { @Override public Mono<Void> writeWith(Flux<DataBuffer> body) { MediaType contentType = originalResponse.getHeaders().getContentType(); // 仅处理JSON类型的响应 if (contentType != null && MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) { // 聚合响应体字节,统一处理 return body.collectList() .flatMap(dataBuffers -> { byte[] originalBytes = aggregateDataBuffers(dataBuffers); try { // 解析JSON并替换字段 JsonNode originalJson = objectMapper.readTree(originalBytes); if (originalJson.has("reason")) { String userMessage = originalJson.get("reason").asText(); byte[] modifiedBytes = objectMapper.writeValueAsBytes( objectMapper.createObjectNode().put("userMessage", userMessage) ); // 更新响应体和Content-Length DataBuffer modifiedBuffer = originalResponse.bufferFactory().wrap(modifiedBytes); originalResponse.getHeaders().setContentLength(modifiedBytes.length); return super.writeWith(Mono.just(modifiedBuffer)); } } catch (Exception e) { // JSON解析失败,返回原始响应 return super.writeWith(Mono.just(originalResponse.bufferFactory().wrap(originalBytes))); } // 无reason字段,返回原始响应 return super.writeWith(Mono.just(originalResponse.bufferFactory().wrap(originalBytes))); }); } else { // 非JSON类型响应,直接放行 return super.writeWith(body); } } }; // 替换响应对象,继续执行过滤器链 return chain.filter(exchange.mutate().response(decoratedResponse).build()); } // 聚合多个DataBuffer为字节数组 private byte[] aggregateDataBuffers(java.util.List<DataBuffer> dataBuffers) { int totalLength = dataBuffers.stream().mapToInt(DataBuffer::readableByteCount).sum(); byte[] result = new byte[totalLength]; int offset = 0; for (DataBuffer buffer : dataBuffers) { int readable = buffer.readableByteCount(); buffer.read(result, offset, readable); offset += readable; buffer.release(); // 释放缓冲区资源 } return result; } @Override public int getOrder() { // 设置优先级高于NettyWriteResponseFilter,确保在响应写出前完成修改 return org.springframework.cloud.gateway.filter.NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1; } }
关键细节说明
- 状态码判断:通过
HttpStatus.isError()快速识别4xx/5xx错误响应,非错误响应直接放行 - Content-Type校验:使用
MediaType.APPLICATION_JSON.isCompatibleWith()兼容所有JSON变种(如application/*+json) - 响应体聚合:将Flux
转换为字节数组,方便JSON解析和修改 - 容错机制:捕获JSON解析异常,直接返回原始响应,避免因无效JSON导致服务报错
- Order设置:通过
WRITE_RESPONSE_FILTER_ORDER -1确保过滤器在响应写出前执行,保证修改生效且不影响状态码
解决你之前的问题
- 状态码异常:过滤器仅修改响应体,不修改原始状态码,解决之前返回200的问题
- 非JSON Payload失败:仅对JSON类型响应处理,其他类型直接放行,避免解析错误
- 全局生效:作为GlobalFilter自动全局生效,无需在每个路由单独配置
内容的提问来源于stack exchange,提问作者rojanu
相关产品推荐
相关产品推荐

