最近我发现,如果我们在accept-header中添加一些参数,由于org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor的isCompatibleWith()方法,Spring很难获得正确的响应头。
例如,如果我们有一个这样的端点:
@ApiResponse(code = ...)
@RequestMapping(value = {/Id}, method = RequestMethod.GET, produces = {
MediaType.APPLICATION_JSON_VALUE + "; profile='http://profiles/v1'",
MediaType.APPLICATION_JSON_VALUE + "; profile='http://profiles/v2'",
MediaType.APPLICATION_JSON_VALUE + "; profile='http://profiles/v3'",})
@ResponseBody
public HttpEntity ..> getXXX(@PathVariable(value = "Id") String Id) throws Exception {
// business logic
}
现在,如果在请求标头中,我给出:
Accept = " "或No Accept header =>,它将按预期返回Content-Type为"application/json; profile='http://profiles/v1'" 。
Accept = "application/json; profile='http://profiles/v3'" =>它将返回相同的Content-Type,因为producibleMediaTypes存在匹配项。
Accept = "application/json; profile='http://invalid/profiles/v1'" => producibleMediaTypes没有匹配项,如预期的那样,它应该返回默认的Content-Type,它是producibleMediaTypes的第一个。 但是现在,它将返回与我们在Request-Header中提供的值完全相同的值,对于上面的值,我将获得"application/json; profile='http://invalid/profiles/v1'"
调试后,我发现:
对于第一种情况, Spring在进入writeWithMessageConverters()之前将自动将Accept-Header设置为"*/*" ,这意味着在与producibleMediaTypes匹配后,它将基于其比较策略传递默认的Content-Type。
对于第二种情况,由于它在producibleMediaTypes具有完美的匹配,它将返回正确的Content-Type。
对于Error场景,这是Spring处理@ResponseBody :
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;
}
requestedMediaTypes就是我们在请求标题过去了, producibleMediaTypes就是我们在提供@ReqeustMapping ,我的理解是,在比较之后,***春*会选择更具体的一个作为selectedMediaType这将是返回响应头。 但是,总是选择无效的作为更具体的一个。
这是有关Github的一些讨论:
似乎他们想改进兼容策略,或者以某种方式允许开发人员实现自己的参数匹配,而不是等待它,还有其他方法可以处理无效的content-type参数吗?
就像是否有任何方法在匹配之前获取producibleMediaTypes ? 如果是这样,我们可以将无效的内容类型标记为"*/* ,那么我们可以按预期获得默认的内容类型。还是可以在将其传递给Response-Header之前再次检查该内容类型?
您以前遇到过这种情况吗? 任何建议或经验都会有所帮助!