最近遇到一个需求,需要根据上传文件的类型,设置response的Content-Type,代码的简单示例如下!
@RequestMapping("/test")
@ResponseBody
public String test(HttpServletRequest request, HttpServletResponse response) throws IOException {
// ......
switch (fileExt) {
case "JPEG":
case "JPG":
response.setContentType(MediaType.IMAGE_JPEG_VALUE);
break;
case "GIF":
response.setContentType(MediaType.IMAGE_GIF_VALUE);
break;
case "PNG":
response.setContentType(MediaType.IMAGE_PNG_VALUE);
break;
default:
break;
}
// .......
return "OK";
}
但是,在进行测试时,发现接口响应的Content-Type始终是text/plain,导致浏览器渲染异常。
对Springboot源码一步一步调试,最终找到问题原因:Springboot会根据接口的返回值类型,推断设置response的Content-Type。
已上述代码为例,返回值为String类型,在 AbstractMessageConverterMethodProcessor#writeWithMessageConverters
方法中,selectedMediaType为text/plain,最后通过消息转换器的 AbstractHttpMessageConverter#write
方法,设置response的Content-Type
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// ...
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter)converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
} else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
// ...
}
public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
// 设置Content-Type
addDefaultHeaders(headers, t, contentType);
// ...
}
所以,出现上述问题的原因就在于接口返回了一个字符串,对这个接口来说,返回值根本就是多余的,修改为void,问题解决。
拓展:设置Content-Type的其他方式
通过 @GetMapping
等注解的 produces
属性,来指定响应的MIME类型。
@GetMapping(value = "/test", produces = MediaType.TEXT_HTML_VALUE)
@ResponseBody
public String test1(HttpServletResponse response) throws IOException {
return "<h1>html</h1>";
}
@GetMapping(value = "/test", produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
public String test2(HttpServletResponse response) throws IOException {
return "<h1>plain</h1>";
}
客户端发起HTTP请求时,会在请求头中包含一个Accept字段,指示它可以接受哪些媒体类型(MIME Type)。如果响应的Content-Type与Accept不匹配,则会返回406 Not Acceptable。
produces
属性的作用就是让框架根据请求头里的Accept属性决定由哪个方法来处理请求。在上述示例中,第一个方法只响应Accept为text/html的GET请求,第二个方法则只响应Accept为text/plain的GET请求。