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

ExceptionHandler中ServerRequest请求体获取失败:媒体类型不兼容问题排查

问题分析

你遇到的问题主要有两个核心原因:

  1. 请求体已被消费:在正常请求处理流程中,原始请求的body已经被订阅读取过(比如你反序列化为业务对象时),而WebFlux的ServerRequest请求体是一次性的流,无法重复订阅读取,这会导致异常处理阶段读取body时出现异常,或者拿到空内容。
  2. 媒体类型支持不足:默认的StringHttpMessageReader通常只支持text/plain这类文本媒体类型,当请求是application/xml或application/json时,没有对应的reader能将其转换为String,所以抛出了Content type 'application/json' not supported的错误。

另外你看到错误提示里是application/json,但实际发的是XML,这大概率是因为错误处理上下文默认的响应媒体类型被设置为JSON,或者请求在异常传递过程中媒体类型被覆盖了。

解决方案

我们可以通过两个步骤来解决这个问题:缓存请求体,以及配置支持所有媒体类型的StringHttpMessageReader。

步骤1:添加请求体缓存过滤器

首先需要在请求进入业务处理前,将请求体缓存起来,这样异常处理阶段可以重复读取。创建一个WebFilter:

@Component
public class RequestBodyCachingFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        // 仅对POST/PUT等带请求体的方法做缓存
        if (exchange.getRequest().getMethod() == HttpMethod.POST || exchange.getRequest().getMethod() == HttpMethod.PUT) {
            return ServerWebExchangeUtils.cacheRequestBody(exchange, (cachedExchange) -> {
                ServerHttpRequest cachedRequest = cachedExchange.getRequest();
                // 将缓存后的请求传递给后续处理链
                return chain.filter(cachedExchange.mutate().request(cachedRequest).build());
            });
        }
        return chain.filter(exchange);
    }
}

这个过滤器会把请求体暂存到内存中,确保后续异常处理时能再次读取。

步骤2:配置支持全媒体类型的StringHttpMessageReader

修改你的ExceptionResolver,在初始化时添加一个支持所有媒体类型的StringHttpMessageReader,并确保它优先被使用:

@Component 
@Order(-2) 
public class ExceptionResolver extends AbstractErrorWebExceptionHandler { 
    public ExceptionResolver(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, 
                             ApplicationContext applicationContext, ServerCodecConfigurer configurer) { 
        super(errorAttributes, resourceProperties, applicationContext); 
        // 创建支持所有媒体类型的StringHttpMessageReader
        StringHttpMessageReader stringReader = new StringHttpMessageReader();
        stringReader.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
        // 将自定义的StringReader加入writers列表头部,确保优先匹配
        List<HttpMessageWriter<?>> writers = new ArrayList<>(configurer.getWriters());
        writers.add(0, stringReader);
        this.setMessageWriters(writers);
    } 
    @Override 
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { 
        return RouterFunctions 
            .route(RequestPredicates.all(), request -> 
                request.bodyToMono(String.class)
                    .onErrorResume(e -> {
                        // 处理读取失败的情况,比如请求体为空或无法读取
                        System.out.println("读取请求体失败: " + e.getMessage());
                        return Mono.just("无法获取请求体");
                    })
                    .flatMap(r -> ServerResponse.ok()
                        .contentType(MediaType.APPLICATION_XML)
                        .syncBody(r))); 
    } 
}

这里做了两个关键调整:

  • 自定义了支持MediaType.ALL的StringHttpMessageReader,确保它能处理任意媒体类型的请求体转String。
  • 新增onErrorResume处理读取失败的场景,避免异常处理逻辑本身抛出异常。
额外提示
  • 如果你不需要支持所有媒体类型,可以把MediaType.ALL替换成你需要的具体类型,比如MediaType.APPLICATION_XML、MediaType.APPLICATION_JSON等。
  • 缓存请求体可能会带来内存压力,如果你的请求体体积较大,建议限制缓存的大小,或者只对特定路径的请求做缓存。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:42:20