Python解析multipart/form-data并保留部件顺序的标准方案
我正在搭建一台可接收多张输入图片的HTTP服务器,图片可通过文件或缓冲区形式传入。例如请求命令如下:
$ curl -F image_file=@/path/to/image1.jpg \ -F image_file=@/path/to/image2.png \ -F image_buffer=/path/to/image_buffer.bin -F image_buffer_shape="256,256,3" \ -F image_file=@/path/to/image4.tif \ <server URL>
该命令会发送multipart/form-data类型的HTTP POST请求,包含5个按客户端发送顺序排列的部件:image_file, image_file, image_buffer, image_buffer_shape, image_file。
使用Python内置函数cgi.parse_multipart时,返回的是列表组成的字典,会丢失不同名称输入的顺序,示例结果如下:
{'image_file' : [<content of image1.jpg>, <content of image2.png>, <content of image4.tif>], 'image_buffer' : [<content of image_buffer.bin>], 'image_buffer_shape' : ['256,256,3'] }
这样就无法得知image4.tif是在image_buffer之后传入的。
最初考虑使用Python内置cgi模块解析请求头和multipart负载,但该模块已被废弃。解析请求头无需使用cgi模块,BaseHTTPRequestHandler.do_POST中的self.headers属于http.client.HTTPMessage类(email.message.Message的子类),可通过self.headers.get_content_type()、self.headers.get_boundary()等标准方法解析。
请问解析multipart负载并保留各输入原始顺序的最标准、最优雅的方式是什么?
方式一:基于标准库email手动解析
利用Python内置的email模块实现,无需额外依赖,能严格保留部件顺序:
- 从请求头获取multipart边界(需添加
--前缀) - 读取完整的请求体内容
- 用边界拆分负载,过滤掉首尾无效的边界行
- 对每个有效部件用
email.message_from_bytes()解析 - 从
Content-Disposition头提取部件名称,解码获取原始内容
示例代码片段:
from email import message_from_bytes def parse_multipart_keep_order(self): content_type = self.headers.get_content_type() if content_type != 'multipart/form-data': return [] boundary = self.headers.get_boundary() if not boundary: return [] # 读取请求体内容 content_length = int(self.headers.get('Content-Length', 0)) payload = self.rfile.read(content_length) # 拆分部件并过滤无效内容 boundary_bytes = f'--{boundary}'.encode() parts = payload.split(boundary_bytes) valid_parts = [part.strip() for part in parts if part.strip() and not part.strip().startswith(b'--')] ordered_parts = [] for part_bytes in valid_parts: msg = message_from_bytes(part_bytes) part_name = msg.get_param('name', header='content-disposition') part_content = msg.get_payload(decode=True) ordered_parts.append((part_name, part_content)) return ordered_parts
返回的ordered_parts是一个列表,每个元素为(部件名称, 内容)的元组,完全匹配客户端发送的顺序。
方式二:使用第三方库requests-toolbelt解析
如果允许引入第三方库,requests-toolbelt的MultipartDecoder封装了完整的解析逻辑,代码更简洁:
from requests_toolbelt.multipart import MultipartDecoder def parse_multipart_with_toolbelt(self): content_type = self.headers.get('Content-Type') content_length = int(self.headers.get('Content-Length', 0)) payload = self.rfile.read(content_length) decoder = MultipartDecoder(payload, content_type) ordered_parts = [] for part in decoder.parts: # 从Content-Disposition头提取部件名称 disp_header = part.headers.get('Content-Disposition', '') if 'name="' in disp_header: part_name = disp_header.split('name="')[1].split('"')[0] ordered_parts.append((part_name, part.content)) return ordered_parts
该库能自动处理边界、编码等细节,同时严格保留部件的原始顺序。
内容的提问来源于stack exchange,提问作者SomethingSomething

