Could not extract response: no suitable HttpMessageConverter found for content type [text/html]

目录

    • 报错信息
    • 源码分析
    • 解决方法
      • 修改 mappingJackson2HttpMessageConverter 配置
      • 继承 mappingJackson2HttpMessageConverter
      • 实现 HttpMessageConverter
      • 继承 AbstractHttpMessageConverter

        如果是使用 OpenFeign 进行远程调用的时候,报以下错误 no suitable HttpMessageConverter

        可考虑修改 feign 接口,如下,使用注解 @ResponseBody、@RequestBody

        @FeignClient("gulimall-order")
        public interface OrderFeignService { @RequestMapping("/order/order/listWithItem")
            @ResponseBody
            R listWithItem(@RequestBody Map params);
        }
        

        报错信息

        在使用 RestTemplate请求调用的时候,程序报错

        报错信息如下:

        org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class afei.common.utils.R] and content type [text/html;charset=UTF-8]
        	at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:121) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
        	at org.springframework.cloud.openfeign.support.SpringDecoder.decode(SpringDecoder.java:59) ~[spring-cloud-openfeign-core-2.1.3.RELEASE.jar:2.1.3.RELEASE]
        	at org.springframework.cloud.openfeign.support.ResponseEntityDecoder.decode(ResponseEntityDecoder.java:62) ~[spring-cloud-openfeign-core-2.1.3.RELEASE.jar:2.1.3.RELEASE]
        	at feign.optionals.OptionalDecoder.decode(OptionalDecoder.java:36) ~[feign-core-10.2.3.jar:na]
        

        错误信息是未知的ContentType,这个ContentType就是第三方接口返回时候在HTTP头中的Content-Type,如果通过其他工具查看这个接口返回的HTTP头,会发现他的值是 text/html,通常我们见的都是 application/json 类型。(微信接口返回的是text/plain),由于内部没有 HttpMessageConverter 能处理text/html的数据,没有一个实现类的 canRead() 返回 true,所以最后报错

        源码分析

        public T extractData(ClientHttpResponse response) throws IOException { MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
            if (responseWrapper.hasMessageBody() && !responseWrapper.hasEmptyMessageBody()) { MediaType contentType = this.getContentType(responseWrapper);
                try { //拿到messageConverters的迭代器
                    Iterator var4 = this.messageConverters.iterator();
                    while(var4.hasNext()) { //下一个HttpMessageConverter
                        HttpMessageConverter messageConverter = (HttpMessageConverter)var4.next();
                        //如果是GenericHttpMessageConverter接口的实例,继承AbstractHttpMessageConverter会走这个if。
                        if (messageConverter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter genericMessageConverter = (GenericHttpMessageConverter)messageConverter;
                            //判断这个转换器能不能转换这个contentType类型
                            if (genericMessageConverter.canRead(this.responseType, (Class)null, contentType)) { if (this.logger.isDebugEnabled()) { ResolvableType resolvableType = ResolvableType.forType(this.responseType);
                                    this.logger.debug("Reading to [" + resolvableType + "]");
                                }
                                //走到这代表当前的HttpMessageConverter能进行转换,则调用read并返回
                                return genericMessageConverter.read(this.responseType, (Class)null, responseWrapper);
                            }
                        }
                        //还是判断这个转换器能不能进行contentType转换
                        if (this.responseClass != null && messageConverter.canRead(this.responseClass, contentType)) { if (this.logger.isDebugEnabled()) { String className = this.responseClass.getName();
                                this.logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
                            }
                            //走到这代表当前的HttpMessageConverter能进行转换,则调用read并返回
                            return messageConverter.read(this.responseClass, responseWrapper);
                        }
                    }
                } catch (HttpMessageNotReadableException | IOException var8) { throw new RestClientException("Error while extracting response for type [" + this.responseType + "] and content type [" + contentType + "]", var8);
                }
                //走到这抛出异常,所有的消息转换器都不能进行处理。
                throw new UnknownContentTypeException(this.responseType, contentType, response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), getResponseBody(response));
            } else { return null;
            }
        }
        

        messageConverters 集合中就保存着

        在 RestTemplate 构造方法中添加的 HttpMessageConverter 实现类

        解决方法

        修改 mappingJackson2HttpMessageConverter 配置

        重新设置 MappingJackson2HttpMessageConverter 能处理的 MediaType

        @Bean
        public RestTemplate restTemplate(){ RestTemplate restTemplate = new RestTemplate();
            MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
            mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML));
            restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
            return  restTemplate;
        }
        

        MappingJackson2HttpMessageConverter 也是一个 HttpMessageConverter 转换类,但是他不能处理 text/html 的数据,原因是他的父类 AbstractHttpMessageConverter 中的 supportedMediaTypes 集合中没有 text/html 类型,如果有的话就能处理了,通过 setSupportedMediaTypes 可以给他指定一个新的 MediaType 集合

        上面的写法会导致 MappingJackson2HttpMessageConverter 只能处理 text/html 类型的数据

        继承 mappingJackson2HttpMessageConverter

        使用MappingJackson2HttpMessageConverter,只需要给他能处理的MediaType

        public class QQHttpMessageConverter extends MappingJackson2HttpMessageConverter { public QQHttpMessageConverter() { setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML));
            }
        }
        

        然后将这个消息转换器追加到RestTemplate中的 messageConverters

        @Bean
        public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate();
            restTemplate.getMessageConverters().add(new QQHttpMessageConverter());  // 兼容 text/plain
            return restTemplate;
        }
        

        实现 HttpMessageConverter

        直接继承HttpMessageConverter(当然更推荐的是继承 Abstract HttpMessageConverter)来实现

        public interface HttpMessageConverter { /**
             * 根据mediaType判断clazz是否可读
             */
            boolean canRead(Class clazz, @Nullable MediaType mediaType);
            /**
             * 根据mediaType判断clazz是否可写
             */
            boolean canWrite(Class clazz, @Nullable MediaType mediaType);
            /**
             * 获取支持的mediaType
             */
            List getSupportedMediaTypes();
            /**
             * 将HttpInputMessage流中的数据绑定到clazz中
             */
            T read(Class clazz, HttpInputMessage inputMessage)
        			throws IOException, HttpMessageNotReadableException;
            /**
             * 将t对象写入到HttpOutputMessage流中
             */
            void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
        			throws IOException, HttpMessageNotWritableException;
        }
        

        canWrite,write方式是不需要处理的,只管canRead和read就行

        在 canRead 方法中判断了是不是 text/html 类型,是的话就会返回true,Spring 就会调用 read,用来将字节流中的数据转换成具体实体,aClass就是我们最终想要得到的实例对象的Class

        StreamUtils 这个工具类是SpringBoot自带的一个,用来读取InputStream中的数据并返回String字符串,SpringBoott内部很多地方都用到了这个工具类

        import com.fasterxml.jackson.databind.DeserializationFeature;
        import com.fasterxml.jackson.databind.ObjectMapper;
        import org.springframework.http.HttpInputMessage;
        import org.springframework.http.HttpOutputMessage;
        import org.springframework.http.MediaType;
        import org.springframework.http.converter.HttpMessageConverter;
        import org.springframework.http.converter.HttpMessageNotReadableException;
        import org.springframework.http.converter.HttpMessageNotWritableException;
        import org.springframework.util.StreamUtils;
        import java.io.IOException;
        import java.nio.charset.Charset;
        import java.util.Arrays;
        import java.util.List;
        public class QQHttpMessageConverter implements HttpMessageConverter { @Override
            public boolean canRead(Class aClass, MediaType mediaType) { if (mediaType != null) { return mediaType.isCompatibleWith(MediaType.TEXT_HTML);
                }
                return false;
            }
            @Override
            public boolean canWrite(Class aClass, MediaType mediaType) { return false;
            }
            @Override
            public List getSupportedMediaTypes() { return Arrays.asList(MediaType.TEXT_HTML);
            }
            @Override
            public Object read(Class aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException { String json = StreamUtils.copyToString(httpInputMessage.getBody(), Charset.forName("UTF-8"));
                ObjectMapper objectMapper = new ObjectMapper();
                objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                return objectMapper.readValue(json, aClass);
            }
            @Override
            public void write(Object o, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException { }
        }
         

        最后需要要进行配置,getMessageConverters() 会返回现有的 HttpMessageConverter 集合,我们在这个基础上加入我们自定义的 HttpMessageConverter 即可

        @Bean
        public RestTemplate restTemplate(){ RestTemplate restTemplate = new RestTemplate();
            restTemplate.getMessageConverters().add(new QQHttpMessageConverter());
            return  restTemplate;
        }
        

        继承 AbstractHttpMessageConverter

        public class QQHttpMessageConverter extends AbstractHttpMessageConverter { public QQHttpMessageConverter() { super(MediaType.TEXT_HTML);
            }
            @Override
            protected boolean supports(Class aClass) { return true;
            }
            @Override
            protected Object readInternal(Class aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException { String json = StreamUtils.copyToString(httpInputMessage.getBody(), Charset.forName("UTF-8"));
                ObjectMapper objectMapper = new ObjectMapper();
                objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                return objectMapper.readValue(json, aClass);
            }
            @Override
            protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException { }
        }