如何将Content-disposition头设置为附件以便于文件部分?

6

我正在使用Python的requests模块发送一个多部分HTTP POST请求,其中包含表单数据和文件附件。

每个多部分对象的"Content-disposition"头都设置为"form-data",包括文件部分。

我需要表单数据部分的"Content-disposition"头仍然为"form-data",但文件部分的"Content-disposition"头必须为"attachment"而不是"form-data"。

我该如何只更改文件部分的content-disposition头?

我的代码:

#Python 3.7.3 (default, Apr 24 2019, 13:20:13) [MSC v.1915 32 bit (Intel)]
import requests

#USER PARAMETERS
user_name = 'user_account'
password = 'user_password'
token = '45Hf4xGhj'

#REQUESTS PARAMETERS
url = '192.168.0.2'
headers = {'content-type': 'multi-part/form-data'}
data = {'Username':user_name, 'Password':password, 'Token':token}
files = {'settings': ('settings.xml', open('settings.xml', 'rb'), 'app/xml')}

#POST
response = requests.post(url, headers=headers, data=data, files=files)

使用Python requests库时,文件部分(file-part)的头信息如下:

Content-Type: app/xml
Content-Disposition: form-data; name="settings"; filename="settings.xml"

我需要文件段落的标题看起来像这样:

Content-Type: app/xml
Content-Disposition: attachment; name="settings"; filename="settings.xml"

我也尝试通过向文件添加头参数来更改标题:

files = {'settings': ('settings.xml', open('settings.xml', 'rb'),
         'app/xml', {'Content-Disposition':'attachment'})}

但是这没有任何效果。我可以指定任何其他自定义标头并添加它,但如果使用此方法,则不会更改“Content-Disposition”标头。

有什么想法吗?


使用工具包:

m = MultipartEncoder( fields={'Username': user_name, 
                              'Password': password, 
                              'Token': token, 
                    'settings': ('settings', open('settings.xml', 'rb'), 
                                 'app/xml', 
                                {'Content-Disposition':'attachment'}
                                )
                             } 
                    ) 

r = requests.post('http://httpbin.org/post', 
                   data=m, 
                   headers={'Content-Type': m.content_type}) 

results in

...--2ba9624051854b6d961bad262a1792fc 
Content-Disposition: form-data; name="settings"; filename="settings"
Content-Type: app/xml 
<?xml version="1.0" encoding="utf-16"?>...

阅读有关 requests/toolbelt 的内容。 - stovfl
@stovfl 谢谢,我尝试了工具包但没有成功。它仍然无法将“Content-Disposition: form-data”头更改为“Content-Disposition: attachment”。同样,如果我选择任何其他标题名称,它会将该标题添加到文件部分的标题中,但它不会更改“Content-Disposition”。 - user3256235
也许这个答案如何在Python中使用请求发送“multipart/form-data”?可以帮助您。请注意第二条评论:“不设置Content-Type标头确保请求将其设置为正确的值”。 - stovfl
2个回答

3

问题: 如何为文件部分设置Content-disposition头文件以作为附件?

简短回答:使用python-requests现有的实现方式,无法实现。

最初的回答:

Explanation:

requests/models.py

class RequestEncodingMixin(object):
    ...
    def _encode_files(files, data):
        ...
        rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
        rf.make_multipart(content_type=ft)

Variable fh holdes the 4th tuple item passed from

files = {'settings': (filename, io.BytesIO(b'some,data,to,send\nanother,row,to,send\n'),
         'app/xml', {'Content-Disposition':'attachment'} )}

The rf.header dict get updated passing headers=fh with 'Content-Disposition':....
Calling rf.make_multipart(content_type=ft), at the next line, only passing the 3trd tuple item.

The method make_multipart - urllib3/fields.py is defined as

def make_multipart(
    self, content_disposition=None, content_type=None, content_location=None
):
    self.headers["Content-Disposition"] = content_disposition or u"form-data"
    ...

which replaces self.headers["Content-Disposition"] with the default u"form-data".


可能的解决方案:

  1. Use only urllib3 there you can do

    rf.make_multipart(content_disposition=fh.get("Content-Disposition"), content_type=ft)
    
  2. File a request to urllib3 and/or python-requests to fix this issue.

  3. Patch yourself, either requests/models.py or urlib3/fields.


补丁: def make_multipart

只有在self.headers中不存在默认的Content-Disposition: form-data时才添加。


from urllib3 import fields

def make_multipart(
        self, content_disposition=None, content_type=None, content_location=None
    ):
        if self.headers.get("Content-Disposition") is None:
            self.headers["Content-Disposition"] = content_disposition or u"form-data"

        self.headers["Content-Disposition"] += u"; ".join(
            [
                u"",
                self._render_parts(
                    ((u"name", self._name), (u"filename", self._filename))
                ),
            ]
        )
        self.headers["Content-Type"] = content_type
        self.headers["Content-Location"] = content_location

fields.RequestField.make_multipart = make_multipart

Resulting multipart:

--e96a4935b8d5b2355f1da3070faa4b28
Content-Disposition: attachment; name="settings"; filename="settings.xml"
Content-Type: app/xml

some,data,to,send
another,row,to,send

--e96a4935b8d5b2355f1da3070faa4b28--

使用Python进行测试:3.5 - urllib3: 1.23 - requests: 2.19.1

最初的回答

0

实际上,在MultipartEncoder生成部分头之后修改它们是可能的。

    for part in m.parts:
    hdr = part.headers.decode('utf-8')
    if 'settings.xml' in hdr:
        part.headers = hdr.replace('Content-Disposition: form-data;',
                  'Content-Disposition: attachment;').encode('utf-8')

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