记录一次RestTemplate使用报错
当时情况是这样的,我要用RestTemplate调用一个接口上传文件,报了一个错误,如下图所示。
分析
报错了,那我们就要一步步来看看到底是怎么报的这个错。
上代码。
首先是RestTemplate类
public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
super.doWithRequest(httpRequest);
Object requestBody = this.requestEntity.getBody();
if (requestBody == null) {
/**
* 省略
*/
}
else { //我们的请求是有情求体的,所以走这里
Class<?> requestBodyClass = requestBody.getClass();
Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
HttpHeaders httpHeaders = httpRequest.getHeaders();
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
MediaType requestContentType = requestHeaders.getContentType(); // 这里获取媒体类型,我们是没有设置过的,所以这里为空
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
if (messageConverter instanceof GenericHttpMessageConverter) { // 这里上来就是一个FastJsonHttpMessageConverter
GenericHttpMessageConverter<Object> genericConverter =
(GenericHttpMessageConverter<Object>) messageConverter;
if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
}
logBody(requestBody, requestContentType, genericConverter);
genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest); // 这里就是报错的地方
return;
}
}
}
/**
* 省略下面代码
*/
}
到这里,我们继续来看FastJsonHttpMessageConverter.write()都做了什么事
public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
super.write(o, contentType, outputMessage); //⚠️注意,这里contentType是null
}
继续,org.springframework.http.converter.AbstractHttpMessageConverter#write
public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType); // 这里报错
/**
* 省略
*/
}
继续,org.springframework.http.converter.AbstractHttpMessageConverter#addDefaultHeaders
protected void addDefaultHeaders(HttpHeaders headers, T t, @Nullable MediaType contentType) throws IOException {
if (headers.getContentType() == null) {
MediaType contentTypeToUse = contentType;
if (contentType == null || !contentType.isConcrete()) {
contentTypeToUse = getDefaultContentType(t); // 由于我们没有设置Content-Type,所以这里会去获取默认的Content-Type
}
else if (MediaType.APPLICATION_OCTET_STREAM.equals(contentType)) {
MediaType mediaType = getDefaultContentType(t);
contentTypeToUse = (mediaType != null ? mediaType : contentTypeToUse);
}
if (contentTypeToUse != null) {
if (contentTypeToUse.getCharset() == null) {
Charset defaultCharset = getDefaultCharset();
if (defaultCharset != null) {
contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
}
}
headers.setContentType(contentTypeToUse); //这里设置Content-Type就报错了
}
}
/**
* 省略
*/
}
那我们来看看,上面这里设置默认Content-Type出了什么问题。
org.springframework.http.converter.AbstractHttpMessageConverter#getDefaultContentType
protected MediaType getDefaultContentType(T t) throws IOException {
List<MediaType> mediaTypes = this.getSupportedMediaTypes();
return !mediaTypes.isEmpty() ? (MediaType)mediaTypes.get(0) : null; // 可以看到获取的是第一个支持的MediaType
}
那么问题就来了,第一个支持的MediaType是什么呢?我们来看下我们的设置
HttpMessageConverters httpMessageConverter() {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
SerializerFeature.PrettyFormat,
SerializerFeature.WriteBigDecimalAsPlain,
SerializerFeature.WriteNullStringAsEmpty,
SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.WriteMapNullValue
);
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
fastConverter.setFastJsonConfig(fastJsonConfig);
List<MediaType> mediaTypeList = new ArrayList<>();
mediaTypeList.add(MediaType.ALL); // 只有一个,就是所有,“*/*”
fastConverter.setSupportedMediaTypes(mediaTypeList);
return new HttpMessageConverters(fastConverter);
}
到这里我们知道了,上面获取默认MediaType获取的就是“*/*”。
好,那我们继续,拿到默认类型了,就要设置了。org.springframework.http.HttpHeaders#setContentType
public void setContentType(@Nullable MediaType mediaType) {
if (mediaType != null) {
Assert.isTrue(!mediaType.isWildcardType(), "Content-Type cannot contain wildcard type '*'"); // 就是这里类型判断为“*”导致报错
Assert.isTrue(!mediaType.isWildcardSubtype(), "Content-Type cannot contain wildcard subtype '*'");
set(CONTENT_TYPE, mediaType.toString());
}
else {
remove(CONTENT_TYPE);
}
}
呼,终于找到报错原因了,没想到竟然是FastJsonHttpMessageConverter设置偷懒设置了ALL导致的问题。
解决
问题找到了总要解决的,根据上面的分析,报错的原因就是Content-Type没有设置导致的,那么就设置一下好了。这里就有两种思路,第一种就是请求的时候设置Content-Type,也就不会出现后面获取默认的Conten-Type了,第二种就是修改FastJsonHttpMessageCenverter,不要使用ALL这种,而是使用具体的类型,但是这种永远都是取第一个类型,有点太僵硬,不推荐。
题外话
FastJsonHttpMessageConverter支持的Content-Type不要设置为ALL,比如这里的使用RestTemplate上传文件,即使加上了Content-Type为MultiPart_FORM_DATA,依然会出其他问题。最终还是要修改FastJsonMessageConverter的Content-Type。