前言
在 HTTP/1.1 协议中,使用 POST 请求提交数据时常用的 Content-Type 有以下几种:
application/x-www-form-urlencoded
原生 Form 默认的提交方式, 最常用的一种,支持GET/POST等方法。主要把数据编码成键值对的方式, 并且把特殊字符转义成 utf-8 字符,如空格会被转义成 %20。application/json
由于 JSON 格式所表示的结构化数据远比键值对复杂得多,所以使用 JSON 系列化之后的字符串进行数据交换的方式越来越受人们青睐。特别适合 RESTful 类型的接口。text/xml
使用 XML-RPC(XML Remote Procedure Call) 协议进行数据传输,相比于 JSON 的方式更为臃肿。multipart/form-data
使用 Form 提交小文件, 直接把文件内容放在 Body 中进行传输的方式。考虑到同时上传多个字段或文件,所以需要按照一定规则随机生成或手动指定一个 boundary 用于分割数据,然后按照一定格式、顺序进行排列构成完整的 Body 进行传输。(multipart/form-data 官方定义)
客户端发送 multipart/form-data 请求
假设现在有 ./file_1.txt
和 ./file_2.txt
两个文件,内容分别如下:
1 | # cat ./file_1.txt |
使用 Requests 实现
1 | import requests |
打印出来的 request body 内容是这样的
1 | --bfa60c05b6631915da313e8fb696e7b2 |
其中 bfa60c05b6631915da313e8fb696e7b2
就是上面所提到自动生成的 boundary。
值得注意的是 {'key_1': 'value_1', 'key_2': 'value_2'}
这两个本身是键值对的数据也被自动转成了 multipart/form-data
的编码方式。如果不传 files
字段时,将自动使用 application/x-www-form-urlencoded
的编码方式,所以 request body 内容应该是这样的
1 | key_1=value_1&key_2=value_2 |
在 requests 中数据编码时,只有 data
参数为 None 时才会判断使用 json
参数,所以 data
和 json
两个参数同时存在时,只会编码 data
的数据;但 data
和 files
是可以同时存在的,而且只要有 files
存在,其它键值对数据也会一起使用 multipart/form-data
的编码方式生成 body 数据。
1 | # json 参数将会被忽略 |
使用 AIOHTTP 实现
1 | import asyncio |
打印出来的 request body 如下
1 | --a63b12cbef044b039c5c788b25a71336 |
可以看到 aiohttp 对键值对默认使用了 Content-Type: text/plain
, 即纯文本的方式,这只是不同库的默认值和实现方式有些区别而已。
服务端解析 multipart/form-data 请求
这里服务端使用 Sanic
框架接收数据请求,Sanic
是 python3
中性能非常好异步无阻塞的 web 框架,特别是跟 uvloop
配合着使用,性能上可以发挥到极致。用法跟Flask
非常类似。项目主页: https://github.com/huge-success/sanic。
1 | from sanic import Sanic, response |
输出信息如下
1 | request.files {'file_1': [File(type='multipart/form-data', body=b'test file 1 content!\n', name='file_1.txt')], 'file_2': [File(type='multipart/form-data', body=b'test file 2 content!\n', name='file_2.txt')]} |