【问题标题】:How to send a “multipart/related” with requests in python?如何在 python 中发送带有请求的“多部分/相关”?
【发布时间】:2013-03-22 17:04:14
【问题描述】:

我正在尝试使用 Python 中的请求发送多部分/相关消息。该脚本看起来很简单,除了 requests 似乎只允许发送 multipart/form-data 消息,尽管他们的文档没有明确说明这种或另一种方式。

我的用例是发送带有附件的肥皂。我可以提供一个字典,其中包含两个文件,其内容是测试肥皂消息,以及我要发送的测试文档。第一个包含带有所有说明的肥皂消息,第二个是实际文档。

但是,如果我不指定 headers 值,则在使用 files 选项时,请求似乎只使用 multipart/form-data。但是,如果我指定标头以尝试指定不同的多部分类型,请求似乎不会添加到 mime 边界信息中。

url = 'http://10.10.10.90:8020/foo'
headers = {'content-type': 'multipart/related'}
files = {'submission': open('submission_set.xml', 'rb'), 'document': open('document.txt', 'rb')}
response = requests.post(url, data=data, headers=headers)
print response.text

有没有办法使用请求来完成这项工作?或者还有其他我应该研究的工具吗?

【问题讨论】:

  • 你检查过搜索[python] [python-requests] +multipart后出现的这22个问题吗?
  • @PiotrDobrogost:这些都是关于multipart/form-datarequests 为您处理。这是multipart/related,这不是POST 的常用编码,requests 不会自动处理。

标签: python mime multipart python-requests


【解决方案1】:

您必须自己创建 MIME 编码。您可以使用email.mime 包来做到这一点:

import requests
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

related = MIMEMultipart('related')

submission = MIMEText('text', 'xml', 'utf8')
submission.set_payload(open('submission_set.xml', 'rb').read())
related.attach(submission)

document = MIMEText('text', 'plain')
document.set_payload(open('document.txt', 'rb').read())
related.attach(document)

body = related.as_string().split('\n\n', 1)[1]
headers = dict(related.items())

r = requests.post(url, data=body, headers=headers)

我假设 XML 文件使用 UTF-8,您可能还想为 document 条目设置一个字符集。

requests 只知道如何创建multipart/form-data 帖子正文; multipart/related 不常用。

【讨论】:

  • 谢谢!这很有帮助。实际上我之前尝试过使用email.mime 包,但不知道如何将这两个服务结合在一起。您帮助我将两个备用脚本合二为一!但是似乎有一个小问题,我不知道是服务组合还是其他故障。我收到了Unexpected EOF in prolog at [row,col {unknown-source}]: [1,0],这使得发送的第一个字符看起来像是作为 EOF 进行互操作的。这可能是由于工具链/编码造成的吗?
  • 我完全不知道;听起来像是 XML 解析的问题,但是这个错误并不熟悉。
  • 这看起来很棒,但我的请求似乎永远挂起。有什么线索吗?
  • @zapatilla:很难说。可能是 content-length 标头错误,服务器期望的数据多于您发送的数据?
  • 例子有问题——headers[Content-Type]实际上没有boundary参数。你应该在items() 之前调用as_string() 让它生成它或在MIMEMultipart 构造函数中提供boundary。在 python 3.5.0 上测试。
【解决方案2】:

我正在使用 requests 和 Google Drive API“分段”上传。

email.mime 解决方案不适用于 Google 的 API,因此我深入研究了 requests 源代码,看看它是如何实现 multipart/form-data 主体的。

requests 使用urllib3.filepost.encode_multipart_formdata() 帮助器,可以将其包装以提供multipart/related

from urllib3.filepost import encode_multipart_formdata, choose_boundary

def encode_multipart_related(fields, boundary=None):
    if boundary is None:
        boundary = choose_boundary()

    body, _ = encode_multipart_formdata(fields, boundary)
    content_type = str('multipart/related; boundary=%s' % boundary)

    return body, content_type

现在我们可以使用 encode_multipart_related() 创建一个符合 Google 要求的 (body, content_type) 元组:

import json
from urllib3.fields import RequestField

def encode_media_related(metadata, media, media_content_type):
    rf1 = RequestField(
        name='placeholder',
        data=json.dumps(metadata),
        headers={'Content-Type': 'application/json; charset=UTF-8'},
    )
    rf2 = RequestField(
        name='placeholder2',
        data=media,
        headers={'Content-Type': media_content_type},
    )
    return encode_multipart_related([rf1, rf2])

这是一个完整的示例,它使用我们的 encode_media_related() 将 hello world 文件上传到 Google Drive,使用 google_auth 库。

from google.oauth2 import service_account
import google.auth.transport.requests

credentials = service_account.Credentials.from_service_account_file(
    PATH_TO_SERVICE_FILE,
    scopes=['https://www.googleapis.com/auth/drive.file'],
)
session = google.auth.transport.requests.AuthorizedSession(credentials)

metadata = {
    'mimeType': 'application/vnd.google-apps.document',
    'name': 'Test Upload',
}
body, content_type = encode_media_related(
    metadata,
    '<html><body><p>Hello World!</body></html>',
    'text/html; charset=UTF-8',
)
resp = session.post(
    'https://www.googleapis.com/upload/drive/v3/files',
    data=body,
    params={'uploadType': 'multipart'},
    headers={'Content-Type': content_type},
)

print 'Uploaded to file with id: %s' % resp.json()['id']

【讨论】:

    猜你喜欢
    • 2015-12-19
    • 2019-08-16
    • 1970-01-01
    • 1970-01-01
    • 2018-11-20
    • 1970-01-01
    • 2016-07-03
    • 2017-06-20
    • 2016-05-14
    相关资源
    最近更新 更多