解析从requests post接收到的multipart/form-data数据

20

我正在编写Web服务客户端,使用requests库。我得到的数据是包含文件和text-json的multipart/form-data格式。我不知道如何解析它。在Python中是否有合适的库来解析multipart/form-data格式,还是应该自己编写解析器?

我的代码:

data = {
  "prototypeModel" :('prototypeModel', open(prototypeModel, 'rb'), 'application/octet-stream', {'Expires': '0'}),
  "mfcc_1" : ('mfcc', open(mfcc_1, 'rb'), 'application/octet-stream', {'Expires': '0'}),
  "mfcc_2" : ('mfcc', open(mfcc_2, 'rb'), 'application/octet-stream', {'Expires': '0'}),
  "mfcc_3" : ('mfcc', open(mfcc_3, 'rb'), 'application/octet-stream', {'Expires': '0'}),
}

print( '---------------------- start enroll ----------------------')
testEnrollResponse = requests.post(server+sessionID, files = data, json = declaredParameters)
b'\r\n--c00750d1-8ce4-4d29-8390-b50bf02a92cc\r\nContent-Disposition: form-data; name="playbackHash"\r\nContent-Type: application/octet-stream\r\n\r\n\x16\x00\x00\x00\x00\x00\x00\x00serialization::archive\n\x00\x04\x08\x04 ..... x00\x00R\x94\x9bp\x8c\x00\r\n--c00750d1-8ce4-4d29-8390-b50bf02a92cc\r\nContent-Disposition: form-data; name="usersMFCC"\r\nContent-Type: application/octet-stream\r\n\r\n\x16\x00\x00\x00\x00\x00\x00\x00serialization::archive\n\x00\x04\x08\x04\x08\x01\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x16\x00\x00\x00\x00\x00\x00u\xb .....
x80\xd9\x95Yxn\xd0?\r\n--c00750d1-8ce4-4d29-8390-b50bf02a92cc\r\nContent-Disposition: form-data; name="scoreAndStatus"\r\nContent-Type: application/json; charset=utf-8\r\n\r\n{"lexLikelihood":1.544479046897232,"overallScore":-nan,"playbackLikelihood":-inf,"status":{"errorCode":0,"errorMessage":""}}\r\n--c00750d1-8ce4-4d29-8390-b50bf02a92cc--\r\n'

我用"....."替换了更多的二进制数据。


展示你所得到的回应。 - miles82
4个回答

26

如果您收到一个multipart/form-data响应,您可以使用requests-toolbelt库进行解析,方法如下:

$ pip install requests-toolbelt

安装完成后

from requests_toolbelt.multipart import decoder

testEnrollResponse = requests.post(...)
multipart_data = decoder.MultipartDecoder.from_response(testEnrollResponse)

for part in multipart_data.parts:
    print(part.content)  # Alternatively, part.text if you want unicode
    print(part.headers)

9

Flask的代码示例,使用https://github.com/defnull/multipart

import multipart as mp
from multipart import tob

try:
    from io import BytesIO
except ImportError:
    from StringIO import StringIO as BytesIO

@app.route('/', methods=["GET","POST"])
def index():
        ...
        elif flask.request.method == "POST":
                data = flask.request.data
                s = data.split("\r")[0][2:]
                p = mp.MultipartParser(BytesIO(tob(data)),s)
                blob = p.parts()[0].value
                f = open("file.bin","wb")
                f.write(blob.encode("latin-1"))
                f.close()

是的,你可以适应我的代码作为Web服务客户端。我的代码没有使用python-requests模块。而且它是工作的。 - Alexander Lubyagin
但这与问题无关。 - uphill
它包含正确的信息,但并没有以任何方式回答问题。 - uphill
2
这篇文章包含有用的信息,事实上当我在寻找类似于服务器端的东西时,这篇文章就出现了。甚至问题是双向的,它说可以接收来自客户端的请求。 - Rahul
请查看提交记录:https://github.com/defnull/multipart/commit/5b868128bf4c18572b0a608c00f5b9d888080eee 将tob重命名为to_bytes,修复了一个语法错误的拼写错误 - Alexander Lubyagin
显示剩余2条评论

7

以下是解析多部分数据的工作示例。您可以在交互式Python提示符下尝试它。

import email

msg = email.message_from_string('''\
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="    XXXX"

--    XXXX
Content-Type: text/plain


--    XXXX
Content-Type: text/plain

--    XXXX--
''')

msg.is_multipart()

一旦您知道它在您的系统上运行,您可以使用POST数据构建自己的电子邮件消息,并以相同的方式解析它。如果您将原始post body作为字符串,则其余必要信息可以在请求标头中找到。我在此处添加了缩进以清晰显示,块字符串中不应有多余的缩进。
    epost_data = '''\
MIME-Version: 1.0
Content-Type: %s

%s''' % (self.headers['content-type'], post_data)

    msg = email.message_from_string(post_data)

    if msg.is_multipart():
        for part in msg.get_payload():
            name = part.get_param('name', header='content-disposition')
            filename = part.get_param('filename', header='content-disposition')
            # print 'name %s' % name # "always" there
            # print 'filename %s' % filename # only there for files...
            payload = part.get_payload(decode=True)
            print payload[:100] # output first 100 characters

第一个%s将被替换为内容类型,第二个将被替换为post_data。然后,您可以将有效负载写入文件等。请注意考虑保存文件的安全性问题。您可能无法信任发布的文件名,例如在某些Web服务器上,它可能以../../filename.sh开头,因此如果您尝试编写/my-folder/../../filename.sh,攻击者可能会在您尝试存储文件的位置之外放置恶意文件。强烈建议在信任文件本身之前对允许的文件类型进行强制验证。您不希望让攻击者覆盖系统上的任何文件。

1
据我所知,最简单的方法是使用@Ian Stapleton Cordasco最初建议的requests-toolbelt
以下是我处理此任务的完整示例:
import requests

payload = f"""<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:aa="http://someservice.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <aa_GetFile>
         <FileID>123</FileID>
      </aa_GetFile>
   </soapenv:Body>
</soapenv:Envelope>"""

headers = {"Content-Type": "text/xml; charset=utf-8"}

response = requests.post("http://localhost:8080/Service?WSDL", data=payload, headers=headers, verify=False)


from requests_toolbelt.multipart import decoder
multipart_data = decoder.MultipartDecoder.from_response(response)


file_content: bytes = b""
for part in multipart_data.parts:
    is_file_part: bool = False
    for key, value in part.headers.items():
        if key.decode("utf8") == "Content-Type":
            if value.decode("utf8") == "application/octet-stream":
                is_file_part = True
    if is_file_part:
        file_content += part.content

print("file_content = " + str(file_content))

结果是:

file_content = b'abcdef'

这是从服务下载的文件的二进制内容。:)


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