即使我已经给出了结果,在Accept-Header中有参数时也会得到错误的Content-Type

时间:2019-02-11 20:01:11

标签: java spring spring-mvc http-headers mime-types

最近我发现,如果我们在accept-header中添加一些参数,由于isCompatibleWith()中的org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor方法,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

}    

现在,如果在请求标头中,我给出:

  1. Accept = " "No Accept header =>它将按预期返回内容类型为"application/json; profile='http://profiles/v1'"
  2. Accept = "application/json; profile='http://profiles/v3'" =>由于producibleMediaTypes中存在匹配项,它将返回相同的Content-Type。
  3. Accept = "application/json; profile='http://invalid/profiles/v1'" => producibleMediaTypes中没有匹配项,如预期的那样,它应该返回默认的Content-Type,它是producibleMediaTypes中的第一个。但是现在,它将返回与我们在Request-Header中提供的值完全相同的值 ,对于上述内容,我将获得"application/json; profile='http://invalid/profiles/v1'"

调试后,我发现:

  1. 对于第一种情况, Spring 会在进入"*/*"之前自动将Accept-Header设置为writeWithMessageConverters(),这意味着与producibleMediaTypes匹配,它将根据其比较策略传递默认的Content-Type。
  2. 对于第二种情况,由于它在producibleMediaTypes中具有完美匹配,因此它将返回正确的Content-Type。
  3. 对于错误场景,这是 Spring 处理@ResponseBody的方式:
protected <T> 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<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
   List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType);

   if (returnValue != null && producibleMediaTypes.isEmpty()) {
      throw new IllegalArgumentException("No converter found for return value of type: " + returnValueClass);
   }

   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 (returnValue != null) {
         throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
      }
      return;
   }

requestedMediaTypes是我们在请求标头中传递的内容,producibleMediaTypes是我们在@ReqeustMapping中提供的内容,经过比较,我理解的是 *** Spring * < / em>将选择更具体的一个作为selectedMediaType,它将作为Response-Header返回。但是,总是选择无效的作为更具体的一个。


以下是有关Github的讨论:

Use parameters declared in consumes or produces condition to narrow the request mapping [SPR-17133]

Content negotiation ignores media type parameters [SPR-10903]

似乎他们想改进兼容策略,或者以某种方式允许开发人员实现自己的参数匹配,而不是等待它,还有其他方法来处理无效的content-type参数吗?

是否有任何方法可以在匹配之前获取producibleMediaTypes?如果是这样,我们可以将无效的内容类型标记为"*/*,则可以按预期获取默认内容类型。还是可以在将内容类型传递给Response-Header之前再次检查它的内容类型?

您以前遇到过这种情况吗?任何建议或经验都会有所帮助!

0 个答案:

没有答案