Python-Requests支持OrderedDict吗,还是这里出了其他问题?

2 投票
2 回答
2921 浏览
提问于 2025-04-17 19:39

我正在尝试用Python的Requests库向亚马逊S3的一个接口发送POST请求。这个请求是multipart/form-data类型的,因为它需要上传一个实际的文件。

我正在使用的API有一个要求,就是file这个参数必须最后发送。由于Requests库使用字典来发送multipart/form-data,而字典的顺序是没有固定的,所以我把它转换成了一个叫payload的有序字典(OrderedDict)。在发送之前,它看起来像这样:

{'content-type': 'text/plain',
 'success_action_redirect':     'https://ian.test.instructure.com/api/v1/files/30652543/create_success?uuid=<opaque_string>',
 'Signature': '<opaque_string>',
 'Filename': '',
 'acl': 'private',
 'Policy': '<opaque_string>',
 'key': 'account_95298/attachments/30652543/log.txt',
 'AWSAccessKeyId': '<opaque_string>',
 'file': '@log.txt'}

这是我发送请求的方式:

r = requests.post("https://instructure-uploads.s3.amazonaws.com/", files = payload)

但是我收到的响应是500错误,所以我不太确定问题出在哪里。我猜可能和我在Requests中使用OrderedDict有关——我找不到任何文档说明Requests是否支持OrderedDict。也有可能是其他原因。

你觉得还有什么可能导致请求失败的地方吗?如果需要,我可以提供更多细节。

好的,更新一下,根据Martijn Pieters之前的评论:

我改变了引用log.txt文件的方式,把它添加到已经创建的upload_data字典中,像这样:

upload_data['file'] = open("log.txt")

打印出结果字典后,我得到了这个:

{'AWSAccessKeyId': '<opaque_string>',
 'key': '<opaque_string>',
 'Policy': '<opaque_string>',
 'content-type': 'text/plain',
 'success_action_redirect': 'https://ian.test.instructure.com/api/v1/files/30652688/create_success?uuid=<opaque_string>',
 'Signature': '<opaque_string>',
 'acl': 'private',
 'Filename': '',
 'file': <_io.TextIOWrapper name='log.txt' mode='r' encoding='UTF-8'>}

这个file键的值看起来正确吗?

当我把它发送到RequestBin时,我得到了这个,和Martin的例子看起来很相似:

POST /1j92n011 HTTP/1.1
User-Agent: python-requests/1.1.0 CPython/3.3.0 Darwin/12.2.0
Host: requestb.in
Content-Type: multipart/form-data; boundary=e8c3c3c5bb9440d1ba0a5fe11956e28d
Content-Length: 2182
Connection: close
Accept-Encoding: identity, gzip, deflate, compress
Accept: */*

--e8c3c3c5bb9440d1ba0a5fe11956e28d
Content-Disposition: form-data; name="AWSAccessKeyId"; filename="AWSAccessKeyId"
Content-Type: application/octet-stream

<opaque_string>
--e8c3c3c5bb9440d1ba0a5fe11956e28d
Content-Disposition: form-data; name="key"; filename="key"
Content-Type: application/octet-stream

<opaque_string>
--e8c3c3c5bb9440d1ba0a5fe11956e28d
Content-Disposition: form-data; name="Policy"; filename="Policy"
Content-Type: application/octet-stream

<opaque_string>
--e8c3c3c5bb9440d1ba0a5fe11956e28d
Content-Disposition: form-data; name="content-type"; filename="content-type"
Content-Type: application/octet-stream

text/plain
--e8c3c3c5bb9440d1ba0a5fe11956e28d
Content-Disposition: form-data; name="success_action_redirect"; filename="success_action_redirect"
Content-Type: application/octet-stream

https://ian.test.instructure.com/api/v1/files/30652688/create_success?uuid=<opaque_string>
--e8c3c3c5bb9440d1ba0a5fe11956e28d
Content-Disposition: form-data; name="Signature"; filename="Signature"
Content-Type: application/octet-stream

<opaque_string>
--e8c3c3c5bb9440d1ba0a5fe11956e28d
Content-Disposition: form-data; name="acl"; filename="acl"
Content-Type: application/octet-stream

private
--e8c3c3c5bb9440d1ba0a5fe11956e28d
Content-Disposition: form-data; name="Filename"; filename="Filename"
Content-Type: application/octet-stream


--e8c3c3c5bb9440d1ba0a5fe11956e28d
Content-Disposition: form-data; name="file"; filename="log.txt"
Content-Type: text/plain

This is my awesome test file.
--e8c3c3c5bb9440d1ba0a5fe11956e28d--

然而,当我尝试把它POST到https://instructure-uploads.s3.amazonaws.com/时,我仍然收到500错误。我还尝试过直接把打开的文件对象添加到files中,然后通过data提交其他值,但那也没有成功。

2 个回答

3

你需要把你要发送的数据分成两个部分,一个是放在 data 里的有序字典,另一个是放在 files 里的。目前,AWS 正确地把你的数据参数当成了文件,而不是表单参数。应该像这样:

data = OrderedDict([
    ('AWSAccessKeyId', '<opaque_string>'),
    ('key', '<opaque_string>'),
    ('Policy', '<opaque_string>'),
    ('content-type', 'text/plain'),
    ('success_action_redirect', 'https://ian.test.instructure.com/api/v1/files/30652688/create_success?uuid=<opaque_string>'),
    ('Signature', '<opaque_string>'),
    ('acl', 'private'),
    ('Filename', ''),
])

files = OrderedDict([('file', open('log.txt'))])

requests.post(url, data=data, files=files)
0

你可以传入一个 dict,或者一系列包含两个值的元组。

OrderedDict 可以很简单地转换成这样的序列:

r = requests.post("https://instructure-uploads.s3.amazonaws.com/", files=payload.items())

不过,因为 collections.OrderedDict()dict 的一个子类,所以调用 items() 正好是 requests 在后台做的事情,所以直接传入一个 OrderedDict 实例也是可以的。

因此,可能是其他地方出了问题。你可以通过向 http://httpbin/post 发送请求来验证一下你发送的内容:

import pprint
pprint.pprint(requests.post("http://httpbin.org/post", files=payload.items()).json())

不幸的是,httpbin.org 不会保留顺序。你也可以在 http://requestb.in/ 创建一个专用的 HTTP post bin,它会更详细地告诉你发生了什么。

使用 requestb.in,并将 '@log.txt' 替换为一个打开的文件对象,requests 的 POST 请求会被记录为:

POST /tlrsd2tl HTTP/1.1
User-Agent: python-requests/1.1.0 CPython/2.7.3 Darwin/11.4.2
Host: requestb.in
Content-Type: multipart/form-data; boundary=7b12bf345d0744b6b7e66c7890214311
Content-Length: 1601
Connection: close
Accept-Encoding: gzip, deflate, compress
Accept: */*

--7b12bf345d0744b6b7e66c7890214311
Content-Disposition: form-data; name="content-type"; filename="content-type"
Content-Type: application/octet-stream

text/plain
--7b12bf345d0744b6b7e66c7890214311
Content-Disposition: form-data; name="success_action_redirect"; filename="success_action_redirect"
Content-Type: application/octet-stream

https://ian.test.instructure.com/api/v1/files/30652543/create_success?uuid=<opaque_string>
--7b12bf345d0744b6b7e66c7890214311
Content-Disposition: form-data; name="Signature"; filename="Signature"
Content-Type: application/octet-stream

<opaque_string>
--7b12bf345d0744b6b7e66c7890214311
Content-Disposition: form-data; name="Filename"; filename="Filename"
Content-Type: application/octet-stream


--7b12bf345d0744b6b7e66c7890214311
Content-Disposition: form-data; name="acl"; filename="acl"
Content-Type: application/octet-stream

private
--7b12bf345d0744b6b7e66c7890214311
Content-Disposition: form-data; name="Policy"; filename="Policy"
Content-Type: application/octet-stream

<opaque_string>
--7b12bf345d0744b6b7e66c7890214311
Content-Disposition: form-data; name="key"; filename="key"
Content-Type: application/octet-stream

account_95298/attachments/30652543/log.txt
--7b12bf345d0744b6b7e66c7890214311
Content-Disposition: form-data; name="AWSAccessKeyId"; filename="AWSAccessKeyId"
Content-Type: application/octet-stream

<opaque_string>
--7b12bf345d0744b6b7e66c7890214311
Content-Disposition: form-data; name="file"; filename="log.txt"
Content-Type: text/plain

some
data

--7b12bf345d0744b6b7e66c7890214311--

这表明顺序被正确保留了。

注意,requests 不支持 Curl 特有的 @filename 语法;相反,你应该传入一个打开的文件对象:

 'file': open('log.txt', 'rb')

你可能还想将 content-type 字段设置为标题格式:'Content-Type': ..

如果你仍然收到 500 的响应,检查一下 r.text 的响应文本,看看亚马逊认为哪里出错了。

撰写回答