在 REST API 的 HTTP/1.0 请求中省略 Accept */* 标头是不是是一个错误

Posted

技术标签:

【中文标题】在 REST API 的 HTTP/1.0 请求中省略 Accept */* 标头是不是是一个错误【英文标题】:Is it a bug to omit an Accept */* header in an HTTP/1.0 Request for a REST API在 REST API 的 HTTP/1.0 请求中省略 Accept */* 标头是否是一个错误 【发布时间】:2014-11-15 16:56:33 【问题描述】:

我正在尝试确定 Python 的 urllib.urlopen() 函数在发出简单的 REST API 请求时忽略 HTTP Accept 标头是否是一个错误。

Facebook Graph API 似乎注意到标头是否存在:

GET /zuck HTTP/1.0
Host: graph.facebook.com
Accept: */*

没有接受头,application/json; charset=UTF-8 返回的 content-type 变为 text/javascript; charset=UTF-8。这可能是 Facebook 的 REST API 中的错误,也可能是对缺少的接受标头的合法响应。

我注意到像curl 这样的命令行工具默认使用Accept: */*

$ curl -v https://graph.facebook.com/zuck
> GET /zuck HTTP/1.1
> User-Agent: curl/7.30.0
> Host: graph.facebook.com
> Accept: */*

同样,Python requests package 也使用 Accept: */* 作为默认值:

def default_headers():
    return CaseInsensitiveDict(
        'User-Agent': default_user_agent(),
        'Accept-Encoding': ', '.join(('gzip', 'deflate')),
        'Accept': '*/*',
        'Connection': 'keep-alive',
    )

我认为 curlrequests 添加默认值是有原因的,但我不确定那个原因是什么。

RFC 2616 for HTTP/1.1 表示 */* indicates all media typesif no Accept header field is present, then it is assumed that the client accepts all media types。这似乎表明Accept: */* 是可选的,省略它不会产生任何影响。也就是说,Python 使用的是 HTTP/1.0,RFC 对省略标头的影响保持沉默。

我想确定最佳实践是像 curlrequests 那样包含 Accept: */* 还是像 Python 的 那样省略是否可以urllib.urlopen() 确实如此。

这个问题很重要,因为我可以fix urllib.urlopen()fix urllib.urlopen(),如果它被确定为有问题,或者它与通常使用 HTTP/1.0 实现的 REST API 一起使用是否存在问题:

>>> import httplib
>>> httplib.HTTPConnection.debuglevel = 1
>>> import urllib
>>> u = urllib.urlopen('https://graph.facebook.com/zuck')
send: 'GET /zuck HTTP/1.0\r\nHost: graph.facebook.com\r\nUser-Agent: Python-urllib/1.17\r\n\r\n'

*** 上的相关问题对这个问题没有帮助。 What does 'Accept: */*' mean under Client section of Request Headers? 询问*/* 的含义(我们已经知道它表示所有媒体类型),Send a curl request with no Accept header? 询问如何在 curl 请求中省略接受标头。我的问题集中在您是否应该包含*/* 以及省略它是否是一个错误。

【问题讨论】:

【参考方案1】:

    如果服务对Accept: */* 和不存在Accept 的反应不同,则说明存在错误(您应该发送错误报告)。

    此外,在application/json 上有一个charset 参数也是一个错误;这是一个没有charset 参数的媒体类型。

【讨论】:

【参考方案2】:

正如您所指出的,RFC 2616 已经说明了在没有 Accept 标头的情况下服务的预期行为(这相当于发送 Accept: */*)。从规范中我们可以推断出

    对于 HTTP 客户端来说,不发送和 Accept 标头是完全合法的。 当这种情况发生时,客户端的意图是接受任何数据表示,并且服务端的内容协商决定使用哪一个。

因此,在您的示例中,您的 php 客户端和您调用的服务都没有行为异常。我想那里没有什么可以修复的。

【讨论】:

【参考方案3】:

RFC 规定

Accept request-header 字段可用于指定响应可接受的某些媒体类型。

这意味着标题是可选的,因为它写着can be used

正如你指出的那样,RFC 还说:

如果没有 Accept 头域,则假定客户端接受所有媒体类型。

这意味着省略标头应该被服务器等效解释为发送Accept: */*,在这两种情况下客户端acceptes all media types

有趣的是,两种情况下 facebook 的响应都不同,但我猜这是他们未能正确解释协议。虽然另一方面,这两个响应显然都是对请求的正确响应(我觉得这很有趣)。

我对这个问题有一些一般性的想法(也可能是contribute to the bugfix discussion):

    在Postel LawBe conservative in what you do, be liberal in what you accept from others (often reworded as "Be conservative in what you send, be liberal in what you accept"). 之后,您可以决定更精确并明确添加Accept: */*。您会更准确地帮助服务器,因为他可能误解了协议(就像 facebook 可能所做的那样),缺少的标头将等同于 Accept: */* 只需添加像 Accept: */* 这样可以省略的标头字段,就会为每个请求增加 11 个字节的网络流量,这可能会导致性能问题。在请求中将 Accept: */* 设为默认值可能会使开发人员难以将其从标头中取出以保存到 11 字节。 规范(或标准)与事实标准之间存在差异。显然,根据规范省略标头字段是完美的,另一方面,许多库似乎都包含此字段,并且像 facebook API 之类的服务表现不同,这可以看作是 事实上的标准正在创建和您可以跳入循环并参与创建它。

在使用 HTTP/1.1 时:即使 (1) 和 (3) 代表 fixing the urllib,我也可能会使用 follow the specification 和性能参数 (2) 并省略标题。如上所述,facebook 在这两种情况下的响应都是正确的,因为他们可以将媒体类型设置为他们喜欢的任何内容。 (尽管这种行为看起来是无意的、奇怪的和错误的)

在谈到 HTTP/1.0 时:我会发送接受标头,因为您说它没有在 HTTP/1.0 RFC 中指定,然后我认为 Postel 定律变得更加重要。另一方面,Accept 标头在http 1.0 中只是可选的。 The Accept request-header field can be used to indicate a list of media ranges which are acceptable as a response to the request为什么要默认设置可选的标题?

【讨论】:

请记住,“事实上的”标准在没有标准规则的情况下是很好的。如果有标准,就没有“事实上的”标准,因为任何不符合标准的东西都只是违反(符合的东西就是标准)。我们正在谈论的这个问题领域有一个裁决标准。让我们遵守它。 IMO 在 RFC 中,"can" 不应自动解释为 "optional",正如您所暗示的那样。有像 "MAY/MUST" 这样的关键词。所以我不相信你的第一个解释是一个有效的解释,尽管第二个引用 does 支持可选的情况。这个评论并不是说这个答案是错误的。根据提供的证据,它似乎可选的。我只是说你应该小心你如何解释“can”。单独的第一个引用,没有更多的上下文,是模棱两可的。【参考方案4】:

RFC 7231 已废弃 RFC 2616。

星号“*”字符用于将媒体类型分组为范围,"*/*" 表示所有媒体类型,"type/*" 表示该类型的所有子类型。 ...

没有任何 Accept 标头字段的请求意味着用户代理将接受 任何媒体类型作为响应。

来源:https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2

从RFC 7231,如果我们考虑graph.facebook.com,any 可以解释为最过时或最兼容的,all 可以解释为最新的媒体类型响应和javascript/json MIME 媒体类型列表; text/javascripttext/ecmascriptapplication/javascriptapplication/ecmascriptapplication/json

似乎服务器认为省略 HTTP Accept 标头的用户代理功能较弱且已过时,并且从前 json 时代开始。这可能是它发送已被 application/javascript 废弃的废弃 MIME 媒体类型 text/javascript 的原因。

也就是说,Python 使用的是 HTTP/1.0,RFC 对省略标头的影响保持沉默。 @raymond-hettinger

无论请求是 HTTP/1.0 还是 HTTP/1.1,现代服务器总是以 HTTP/1.1 响应。因此,为了被服务器认为是最新的,用户代理应该在请求中包含 Accept 标头。并且接受标头参与内容协商。

关于charset中的application/json; charset=UTF-8

JSON 文本应以 UTF-8、UTF-16 或 UTF-32 编码。默认编码为 UTF-8。

https://www.rfc-editor.org/rfc/rfc7159#section-8.1

所以这看起来不像是错误。

【讨论】:

这是一个深思熟虑且有趣的答案;但是,我没有看到 RFC 的任何部分支持“any 可以被解释为最过时或最兼容并且 all 可以被解释为最新的解释媒体类型”。您是否有任何支持该解释的特定链接?即使是指向一些以这种方式运行的公共 REST 服务器代码的链接也会有所帮助。【参考方案5】:

阅读代理服务器(如 nginx 和 Varnish)帮助我弄清楚发生了什么。

虽然Accept: */* 标头的存在不应该对服务器产生影响,但当响应包含Vary: Accept 标头时,它可以并且很可能会对代理服务器产生影响。特别是,允许​​代理服务器为不同的或省略的 Accept 标头缓存不同的结果。

自提出此问题以来,Facebook 已更新(并关闭)其 API,但当时,这是导致观察到效果的场景。 For backwards compatibility reasons,Facebook 正在使用内容协商并在收到省略 Accept 标头或具有类似浏览器的 Accept: text/html;text/*;*/* 的请求时以 text/javascript; charset=UTF-8 响应。然而,当它收到Accept: */* 时,它返回了更现代的application/json; charset=UTF-8。当代理服务器接收到一个没有接受头的请求时,它可以给出任何一个缓存响应;但是,当它得到Accept: */* 时,它总是给出最后一个响应。

这就是为什么应该包含Accept: */* 标头:如果这样做,缓存代理将始终返回相同的内容类型。如果省略标头,响应可能会根据最后一个用户的内容协商结果而有所不同。 REST API 客户端往往依赖于每次都获取相同的内容类型。

【讨论】:

以上是关于在 REST API 的 HTTP/1.0 请求中省略 Accept */* 标头是不是是一个错误的主要内容,如果未能解决你的问题,请参考以下文章

如何在React中处理REST API请求

在 REST API 中定义操作

POST请求标头与rest api中敏感数据的请求主体

有没有办法在 REST API Jersey 中检测预检请求?

如何在 REST api 中确定请求的来源

REST API + hacks/REST + RPC 混合。我做对了吗?