HTTP 内容协商是否尊重媒体类型参数

Does HTTP content negotiation respect media type parameters

HTTP 请求可以包含 Accept header,指示客户端可接受的响应媒体类型。服务器应通过提供具有与请求的媒体类型(之一)匹配的 Content-Type 的响应来尊重请求。媒体类型可能包括 参数 。 HTTP 是否要求 content-negotiation 的这个过程遵守 参数

即如果客户端请求

 Accept: application/vnd.example; version=2

(这里version参数的值为2),服务器可以服务media-typeapplication/vnd.example; version=1,但不能服务application/vnd.example; version=2,是服务器可以提供

的响应
 Content-Type: application/vnd.example; version=1

服务器是否可以提供标记为

的响应
 Content-Type: application/vnd.example; version=2

但是响应的 body 实际上被编码为 media-type application/vnd.example; version=1?也就是说,对于响应的media-type的参数是对响应的body的不准确描述?

似乎 Spring MVC 4.1.0 在进行内容协商时不尊重 media-type 参数,并给出响应的 media-type 参数是一个响应的 body 描述不准确。这似乎是因为 org.springframework.util.MimeType.isCompatibleWith(MimeType) 方法没有检查 MimeType objects.

的参数

HTTP/1.1 中内容协商的相关规范是 RFC2616, Section 14.1.

它包含以下示例,与您的问题相关:

Accept: text/*, text/html, text/html;level=1, */*

并给出优先级为

1) text/html;level=1
2) text/html
3) text/*
4) */*

所以我认为可以肯定地说 text/html;level=1text/html 是不同的媒体类型。 我也会认为 text/html;level=1text/html;level=2 是不同的。

因此,在您的示例中,我认为以 406 错误响应而不以其他媒体类型响应是正确的。

相关标准RFC 7231 section 3.1.1.1对media-types的描述如下:

The type/subtype MAY be followed by parameters in the form of name=value pairs.

因此,AcceptContent-Type header 可能包含媒体类型参数。它添加:

The presence or absence of a parameter might be significant to the processing of a media-type, depending on its definition within the media type registry.

这表明使用参数类型的服务器代码应该注意它们,而不是简单地丢弃它们,因为对于某些媒体类型它们 意义重大。它必须在是否考虑媒体类型参数是否重要方面实现一些智能。

Spring MVC 4.1.0 因此在进行内容协商时完全忽略参数似乎是错误的:class org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor 使用 org.springframework.util.MimeType.isCompatibleWith(MimeType) 是不正确的,或者 MimeType.isCompatibleWith(MimeType) 方法不正确。如果您为 Spring 提供几个 HTTP 消息转换器,这些转换器仅在其支持的媒体类型的参数上有所不同,Spring 将不会可靠地选择媒体类型与请求的媒体类型完全匹配的 HTTP 消息转换器.


section 3.1.1.5 中描述 Content-Type header 的地方说:

The indicated media type defines both the data format and how that data is intended to be processed by a recipient

由于媒体类型的参数通常会改变数据格式,Spring MVC 4.1.0 的行为是错误的,因为提供的参数对 body 的描述不准确响应:方法 AbstractMessageConverterMethodProcessor.getMostSpecificMediaType(MediaType, MediaType) 对 return 和 acceptType 是错误的,而不是 produceTypeToUse 当两种类型同样具体时。


然而,讨论内容协商(主动协商)的section 3.4.1,注释:

A user agent cannot rely on proactive negotiation preferences being consistently honored, since the origin server might not implement proactive negotiation for the requested resource or might decide that sending a response that doesn't conform to the user agent's preferences is better than sending a 406 (Not Acceptable) response.

因此服务器 允许给出一个 不完全 匹配请求的 media-type 参数的响应,作为fall-back 当它不能提供精确匹配时。也就是说,它可能会选择用 application/vnd.example; version=1 响应 body、Content-Type: application/vnd.example; version=1 header 来响应,尽管请求说 Accept: application/vnd.example; version=2当且仅当 生成有效的 application/vnd.example; version=2 响应是不可能的。


Spring 的这种明显不正确的行为已经有一个 Spring 错误报告,SPR-10903。 Spring 开发人员将其关闭为“按设计工作”,并指出

I don't know any rule for comparing media types with their parameters effectively. It really depends on the media type...If you're actually trying to achieve REST versioning through media types, it seems that the most common solution is to use different media types, since their format obviously changed between versions:

  • "application/vnd.spring.foo.v1+json"
  • "application/vnd.spring.foo.v2+json"