背景
在对一个遗留老系统使用SpringBoot框架进行重写的过程中,遇到了一个奇怪的问题:即当服务使用SpringBoot的main入口独立启动的时候,接口访问一切正常,但是当项目被打成war包运行在Tomcat中时,调用接口就会返回406 Not Acceptable
错误,而由于运维等层面考虑,服务仍然要在Tomcat中运行一段时间作为过渡,因此不管是从对技术追求的态度上,还是从实际需求出发,这都是个不得不解决的问题。
错误原因分析
要解决问题,首先我们需要知道,406错误出现的直接原因是什么。在一次HTTP请求中,如果服务端对于body内容的类型(即Content-Type)处理上产生了冲突,即会返回406错误状态。
SpringBoot处理Content-Type的具体代码入口在org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor
的方法writeWithMessageConverters
中,其中一段如下:
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
}
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
可看到,其逻辑是,在当前请求可选的MediaType和接口要生成的MediaType中进行匹配,如果匹配不到,即会抛出HttpMediaTypeNotAcceptableException
异常从而导致返回406错误。回到我们的项目,接口都是@RestController注解,因此producibleMediaTypes只能是application/json
这一类,所以我们需要看requestedMediaTypes是什么。
继续深入源码,getAcceptableMediaTypes方法如下:
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
List<MediaType> mediaTypes = this.conte