最近使用spring boot编写api,使用swagger显示并测试,执行的时候其中某一个api在解析multipart/form-data类型的时候发生了如下错误:
Content type 'application/octet-stream' not supported
大概的api代码如下:
@PostMapping(value = "/edit")
public void edit(@RequestPart(value = "info", required = false) List<UserEntity> users,@RequestParam(value = "image", required = false)MultipartFile file) {
//to do
}
为什么会发生这种错误呢?首先可以肯定的是并没有设置application/octet-stream这种content-type,那么错误的来源是哪儿呢?通过追踪错误的堆栈信息,找到类AbstractMessageConverterMethodArgumentResolver中的方法readWithMessageConverters:
@SuppressWarnings("unchecked")
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
});
return body;
}
该方法在开始的时候取content-type,如果找不到的话就设置为application/octet-stream,然后根据content-type找到可以处理该类型的messageConverter,获取body信息。现在我们可以找到为什么会抛出这样的错误信息了,因为没有定义application/octet-stream相关的messageConverter。
所以我们可以通过自定义一个messageConverter解决这个问题,添加的converter的代码如下:
@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
protected MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
protected boolean canWrite(MediaType mediaType) {
return false;
}
}
问题虽然解决了,但是,为什么content-type会是null呢?
我们使用swagger调用的api,查看swagger生成的curl命令发现,请求中image这个参数指定了content-type,但是info却并没有:
curl -X POST "http://127.0.0.1:8080/api/v1.0/edit" -H "accept: */*" -H "Content-Type: multipart/form-data" -F "[email protected];type=image/jpeg" -F "info=[]"
,
然后如果给info添加上content-type,即修改为curl -X POST "http://127.0.0.1:8080/api/v1.0/edit" -H "accept: */*" -H "Content-Type: multipart/form-data" -F "[email protected];type=image/jpeg" -F "info=[];type=application/json"
不添加messageConverter也可以正常运行,所以这应该是swagger的问题,暂时没有找到配置swagger的好办法,所以干脆选择了添加messageConverter来解决上述问题,mark一下。