Python请求无法发送具有相同键的多个标头

12

我正在尝试向具有两个具有相同名称但不同值的标头的服务器发送GET请求:

url = 'whatever'
headers = {'X-Attribute': 'A', 'X-Attribute': 'B'}
requests.get(url, headers = headers)

很明显这样行不通,因为请求头字典不能包含两个键X-Attribute

我能做些什么吗?比如说,我可以将headers传递为字典以外的其他内容吗?以这种方式发送请求是服务器的一个特性,我无法改变它。


2
为什么你想要这样做? - Jon Clements
我发送请求的服务器期望它。它根据传递给它的完整属性集过滤响应,这些属性的格式为 X-Attribute:name=value - Stefan
4个回答

11

requests库将请求头存储在一个dict中,这意味着每个头只能出现一次。因此,如果不对requests库本身进行更改,就无法发送具有相同名称的多个头。

然而,如果服务器符合HTTP1.1标准,它必须能够将其视为一个带有逗号分隔值列表的头。


晚些编辑:
既然引起了我的关注,使这个工作的方法是使用一个自定义的str实例,在字典中存储多个相同值,通过以不同的方式实现哈希协议(或者实际上在CaseInsensitiveDict中使用lower())来实现。例如:

class uniquestr(str):

    _lower = None

    def __hash__(self):
        return id(self)

    def __eq__(self, other):
        return self is other

    def lower(self):
        if self._lower is None:
            lower = str.lower(self)
            if str.__eq__(lower, self): 
                self._lower = self
            else:
                self._lower = uniquestr(lower)
        return self._lower

r = requests.get("https://httpbin.org/get", headers={uniquestr('X'): 'A',
                                                     uniquestr('X'): 'B'})
print(r.text)

生成类似于:

{
  ...
  "headers": {
    ...
    "X": "A,B",
  }, 
  ...
}

有趣的是,在响应中,头信息被合并了,但它们实际上被发送为两行独立的头。


那很有用。可惜,看起来服务器不符合要求! - Stefan
6
我不建议这个作为答案,但你可以尝试利用 requests 没有自动转义传递的头部字典内容的事实,并传递 headers={'X-Attribute': 'name=value\r\nX-Attribute: name=value'}。然而,这并不是一个非常好的解决方案... - mata
如上评论中提到的解决方法已经不再适用于requests 2.11.0及以上版本,因为该版本引入了对头部值中回车符的检查。请参见:https://github.com/psf/requests/commit/2669ab797ce769ecedf5493b04cb976f33e37210 - Dunedan
@Dunedan 是的,那基本上只能通过利用requests中的一个漏洞来实现... - mata

5

requests在底层使用urllib2.urlencode(或类似方法)来编码头信息。

这意味着可以将元组列表作为有效载荷参数发送,而不是字典,从而使头信息列表不再受字典强制的唯一键约束限制。发送元组列表的方法在urlib2.urlencode文档中有说明。http://docs.python.org/2/library/urllib.html#urllib.urlencode

以下代码将解决问题,无需展开或使用不良编程技巧:

url = 'whatever'
headers = [('X-Attribute', 'A'),
           ('X-Attribute', 'B')]
requests.get(url, headers = headers)

4
这似乎不适用于我的requests版本(2.7.0)。 - augurar

3

现在,Requests将所有头信息(发送和接收)都存储在大小写不敏感的字典中。除此之外,在打开Python控制台并编写以下代码:

headers = {'X-Attribute':'A', 'X-Attribute':'B'}

您得到的是未定义行为。(看起来可能是可重复的,但它完全是未定义的。)因此,在那种情况下,您实际发送给请求的是这个:
{'X-Attribute': 'A'}  # or {'X-Attribute': 'B'}, we can never be certain which it will be

您可以尝试(但不会起作用)的方法是:

headers = [('X-Attribute', 'A'), ('X-Attribute', 'B')]

但至少这将是完全定义的行为(您将始终发送B)。正如@mata建议的那样,如果您的服务器符合HTTP/1.1标准,您可以这样做:

import collections

def flatten_headers(headers):
    for (k, v) in list(headers.items()):
        if isinstance(v, collections.Iterable):
           headers[k] = ','.join(v)

headers = {'X-Attribute': ['A', 'B', ..., 'N']}
flatten_headers(headers)
requests.get(url, headers=headers)

我注意到在实践中,HTTP服务器倾向于限制标题行的长度。如果完整的逗号分隔列表对于一行来说过长怎么办?这似乎需要解决原始问题的方案。 - rspeer
@rspeer 那么你应该告诉那个服务器管理员它不符合规定。 目前最好的解决方法是加入它们。由于这种情况的罕见性,我们没有将此功能作为高优先级。 - Ian Stapleton Cordasco

2
url = 'whatever'
headers = {'X-Attribute': "A,B"}
requests.get(url, headers = headers)

我已经几年前就离开了那个项目,但我认为你正在建议 @mata 最初提出的正是这个...一个兼容的服务器需要接受逗号分隔值。该服务器在许多方面都不兼容,包括不接受这些值。 - Stefan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接