java 406,基于SpringMvc实现Restful方式接口返回406的问题

一、问题:

二、问题排查:

HTTP Status 406 -

type Status report

message

description The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.

HTTP Status 406 -

type Status report

message

description The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.

额,问题定位,不能以otg结尾!

为什么呢?

根据提示

description The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.

服务响应不接受请求的accept。这又是为什么呢?request请求header里的accept是什么?如下:

Accept:

text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

以上五个请求都是这个。那为什么前两个不接受,后三个就接受了呢?

其实前两个请求经过spring处理以后就不再按照浏览器或者客户端请求头里面带有的accept做处理了。本来以为4XX开头的都是客户端的问题,跟调用接口的同事还墨迹了一番,后来本地调试居然进到的服务端的处理方法里面了,于是服务器端排查。

首先找到处理@ResponseBody的处理器

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor,定位到写json输出的方法,debug:

protected void writeWithMessageConverters(T returnValue, MethodParameter returnType,

ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)

throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

Class> returnValueClass = getReturnValueType(returnValue, returnType);

Type returnValueType = getGenericType(returnType);

HttpServletRequest servletRequest = inputMessage.getServletRequest();

List requestedMediaTypes = getAcceptableMediaTypes(servletRequest);

List producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType);

if (returnValue != null && producibleMediaTypes.isEmpty()) {

throw new IllegalArgumentException("No converter found for return value of type: " + returnValueClass);

}

Set compatibleMediaTypes = new LinkedHashSet();

for (MediaType requestedType : requestedMediaTypes) {

for (MediaType producibleType : producibleMediaTypes) {

if (requestedType.isCompatibleWith(producibleType)) {

compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));

}

}

}

if (compatibleMediaTypes.isEmpty()) {

if (returnValue != null) {

throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);

}

return;

}

getAcceptableMediaTypes和getProducibleMediaTypes就是我们这边涉及到的accept和pruduce相关的,debug到getAcceptableMediaTypes发现requestedMediaTypes变成了application/vnd.oasis.opendocument.graphics-template,这个是个媒体类型,为什么accept里面会是这个媒体类型呢?

跟踪方法内部,到达org.springframework.web.accept.ContentNegotiationManager类的resolveMediaTypes方法:

@Override

public List resolveMediaTypes(NativeWebRequest request)

throws HttpMediaTypeNotAcceptableException {

for (ContentNegotiationStrategy strategy : this.strategies) {

List mediaTypes = strategy.resolveMediaTypes(request);

if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {

continue;

}

return mediaTypes;

}

return Collections.emptyList();

}

发现这边spring根据策略模式实现了以下六种:

AbstractMappingContentNegotiationStrategy、FixedContentNegotiationStrategy、HeaderContentNegotiationStrategy、

还有三种ParameterContentNegotiationStrategy、PathExtensionContentNegotiationStrategy继承AbstractMappingContentNegotiationStrategy,

ServletPathExtensionContentNegotiationStrategy继承FixedContentNegotiationStrategy。

跟进resolveMediaTypes方法

@Override

public List resolveMediaTypes(NativeWebRequest webRequest)

throws HttpMediaTypeNotAcceptableException {

return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));

}

@Override

protected String getMediaTypeKey(NativeWebRequest webRequest) {

HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);

if (request == null) {

logger.warn("An HttpServletRequest is required to determine the media type key");

return null;

}

String path = PATH_HELPER.getLookupPathForRequest(request);

String filename = WebUtils.extractFullFilenameFromUrlPath(path);

String extension = StringUtils.getFilenameExtension(filename);

return (StringUtils.hasText(extension)) ? extension.toLowerCase(Locale.ENGLISH) : null;

}

String extension = StringUtils.getFilenameExtension(filename);

这边会根据url中的以“.**”结尾的请求获取到扩展名,然后根据org.springframework.web.accept.MappingMediaTypeFileExtensionResolver类的lookupMediaType方法,找到扩展名对应的媒体类型

/**

* Use this method for a reverse lookup from extension to MediaType.

* @return a MediaType for the key, or {@code null} if none found

*/

protected MediaType lookupMediaType(String extension) {

return this.mediaTypes.get(extension.toLowerCase(Locale.ENGLISH));

}

这样拿到requestedMediaTypes,再回到@ResponseBody注解处理器里面,一一遍历requestedMediaTypes和producibleMediaTypes,如果producibleMediaTypes的集合没有匹配到requestedMediaTypes中的数据,就报了description The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.

其实我认为这里面的提示有点不是很直观,提示描述是从header里面获取accept不接受,第一感觉就是客户端代码或者浏览器设置的header头里面的accept。

三、解决过程:

1、尝试过通过filter去改变header的值,改变不了,只能覆盖getHeader方法

2、考虑过继承策略类重写protected abstract String getMediaTypeKey(NativeWebRequest request);方法,还好没有这么做,估计会引发其他问题

四、最终解决方法:

将favorPathExtension设置为false

五、总结:

Restful风格的接口设计,尽量不要把url的最后一个"/"后面设计成参数,即@PathVariable形式接收的参数

不建议的设计:

http://ip:port/xxx/xxxx/{ppp}

建议的设计:

http://ip:port/xxx/{ppp}/xxxx

因为ppp一旦是转义型的字符或者以媒体类型结尾的后缀,需要做一些处理。

(基于springboot做微服务涉及到类似的问题可以参考着处理)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值