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

Spring Gateway如何针对4xx/5xx响应条件化修改响应体?

解决方案

针对你的需求,我们可以通过实现带条件判断的GlobalFilter来精准处理4xx/5xx错误响应,同时避免非JSON类型Payload的解析失败问题。

核心思路

  1. 状态码过滤:仅对4xx/5xx错误状态码的响应进行处理
  2. Content-Type校验:只处理application/json或兼容JSON类型的响应体
  3. 容错处理:JSON解析失败时直接返回原始响应,避免服务异常
  4. 保持状态码:修改响应体的同时保留原始错误状态码

完整代码实现

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确保过滤器在响应写出前执行,保证修改生效且不影响状态码

解决你之前的问题

  1. 状态码异常:过滤器仅修改响应体,不修改原始状态码,解决之前返回200的问题
  2. 非JSON Payload失败:仅对JSON类型响应处理,其他类型直接放行,避免解析错误
  3. 全局生效:作为GlobalFilter自动全局生效,无需在每个路由单独配置

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 20:02:46