ExceptionHandler中ServerRequest请求体获取失败:媒体类型不兼容问题排查
问题分析
你遇到的问题主要有两个核心原因:
- 请求体已被消费:在正常请求处理流程中,原始请求的body已经被订阅读取过(比如你反序列化为业务对象时),而WebFlux的
ServerRequest请求体是一次性的流,无法重复订阅读取,这会导致异常处理阶段读取body时出现异常,或者拿到空内容。 - 媒体类型支持不足:默认的
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
相关产品推荐
相关产品推荐

