如何使用请求将每个文件的自定义标头添加到分段上传?

时间:2013-09-30 22:23:20

标签: python http http-post python-requests multipart

python-requests似乎支持自定义集合作为要上载的文件的列表/映射。

这反过来应该允许我添加自定义标题字段,每个文件在一个分段上传中上传。

我怎么能真正做到这一点?

1 个答案:

答案 0 :(得分:1)

  

python-requests似乎支持自定义集合作为要上载的文件的列表/映射。

     

这反过来应该允许我添加自定义标题字段,每个文件在一个分段上传中上传。

不。列表/映射中的每个文件必须是文件对象(或内容字符串),文件名和文件对象(或内容)的2元组,或文件名,文件对象(或内容)和文件的3元组类型。其他任何事情都是非法的。

除非#1640被上游接受,否则你所要做的就是使用文件名,文件对象(或内容),文件类型和标题字典的4元组。


此时正确的做法可能是使用不同的库。例如,如果您直接使用urllib3而不是request包装器,那么您可以轻松地完成您想要的操作。您只需要处理使用urllib3而不是requests的所有额外问题。

同时,您可以针对requests提交功能请求,并且可能会在将来添加更简单的方法。


但令人沮丧的是,您需要的功能全部都在幕后,您无法实现它。

看起来干净利落做事会是一场噩梦。但这都是非常简单的代码,我们可以很容易地破解它,所以让我们这样做。

看看requests.PreparedRequest.prepare_body。它期望files中的每个文件都是文件名,内容或文件对象的元组,以及可选的内容类型。它基本上只是读入任何文件对象以将它们转换为内容,并将所有内容直接传递给urllib3.filepost.encode_multipart_formdata。因此,除非我们想要替换此代码,否则我们将需要使用其中一个值来走私标头。我们通过传递(filename, contents, (content_type, headers_dict))来做到这一点。所以,requests本身没有变化。

它所称的urllib3.filepost.encode_multipart_formdata怎么样?正如您所看到的,如果您为文件传递元组,它会调用一个名为iter_field_objects的函数,该函数最终会在每个函数上调用urllib3.fields.RequestField.from_tuples。但是如果你看一下from_tuples替代构造函数,它会说它是处理构造RequestField对象的“旧式”方式,而普通构造函数是用于“新式”方式,实际上是让你传入标题。

因此,我们需要做的就是使用monkeypatch iter_field_objects,将其最后一行替换为使用新式方式的行,我们应该完成。我们试试吧:

import requests
import requests.packages.urllib3
from requests.packages.urllib3.fields import RequestField, guess_content_type
import six

old_iter_field_objects = requests.packages.urllib3.filepost.iter_field_objects
def iter_field_objects(fields):
    if isinstance(fields, dict):
        i = six.iteritems(fields)
    else:
        i = iter(fields)

    for field in i:
      if isinstance(field, RequestField):
        yield field
      else:
        name, value = field
        filename = value[0]
        data = value[1]
        content_type = value[2] if len(value)>2 else guess_content_type(filename)
        headers = None
        if isinstance(content_type, (tuple, list)):
            content_type, headers = content_type
        rf = RequestField(name, data, filename, headers)
        rf.make_multipart(content_type=content_type)
        yield rf
requests.packages.urllib3.filepost.iter_field_objects = iter_field_objects

现在:

>>> files = {'file': ('foo.txt', 'foo\ncontents\n'), 
...          'file2': ('bar.txt', 'bar contents', 'text/plain'),
...          'file3': ('baz.txt', 'baz contents', ('text/plain', {'header': 'value'}))}
>>. r = request.Request('POST', 'http://example.com', files=files)
>>> print r.prepare().body
--1ee28922d26146e7a2ee201e5bf22c44
Content-Disposition: form-data; name="file3"; filename="baz.txt"
Content-Type: text/plain
header: value

baz contents
--1ee28922d26146e7a2ee201e5bf22c44
Content-Disposition: form-data; name="file2"; filename="bar.txt"
Content-Type: text/plain

bar contents
--1ee28922d26146e7a2ee201e5bf22c44
Content-Disposition: form-data; name="file"; filename="foo.txt"
Content-Type: text/plain

foo

多田!

请注意,您需要相对最新的requests / urllib3才能使用此功能。我认为requests 2.0.0就足够了。