Flask中request.files仅支持multipart/form-data上传,处理XML需先确认协议类型:multipart时用request.files.get('xml').seek(0)后解析;raw XML时用request.get_data()配合ET.fromstring(),并禁用DTD防XXE。

Flask 中 request.files 只能处理 multipart/form-data,不能直接读 XML 文件
Flask 的 request.files 是专为 multipart/form-data 编码设计的,它依赖表单字段的 name 和文件头(如 Content-Disposition: form-data; name="file"; filename="data.xml")来提取文件。如果你用 fetch 或 curl 直接 POST 原始 XML 字符串(Content-Type: application/xml),request.files 会为空——这不是 Bug,是协议限制。
常见错误现象:request.files.get('xml') 返回 None,但 request.data 或 request.get_data() 却有内容。
- 用
curl -X POST -H "Content-Type: application/xml" --data-binary @data.xml http://localhost:5000/upload→ 必须用request.get_data() - 用 HTML 表单
+enctype="multipart/form-data"→ 才能用request.files['xml'] -
前端用
FormData.append('xml', fileInput.files[0])→ 也走 multipart,可用request.files
从 request.files 读取 XML 文件的正确写法
当确认是 multipart 提交后,request.files 返回的是 FileStorage 对象,不是普通文件句柄。不能直接用 xml.etree.ElementTree.parse() 传它,必须先定位到可读流位置(尤其多次读取时容易出错)。
from flask import Flask, request import xml.etree.ElementTree as ETapp = Flask(name)
@app.route('/upload', methods=['POST']) def upload_xml(): xml_file = request.files.get('xml') if not xml_file: return 'No file uploaded', 400
确保从开头读(避免因 prior read 导致空内容)
xml_file.seek(0) try: tree = ET.parse(xml_file) root = tree.getroot() return f'XML parsed: {root.tag}', 200 except ET.ParseError as e: return f'Invalid XML: {e}', 400
-
xml_file.seek(0)很关键:某些中间件或日志记录可能已读过一次流,不重置会导致ET.parse()报ParseError: no element found - 不要用
xml_file.filename做信任校验——客户端可伪造,只用于日志或调试 - 若需保存原始文件,用
xml_file.save('/path/to/save.xml'),但注意路径安全(过滤..、检查扩展名)
Content-Type 不匹配时的 fallback 处理逻辑
生产环境常遇到前端发错类型(比如该发 multipart 却发了 raw XML),硬性拒绝不如优雅降级。可按优先级依次尝试:request.files → request.get_data(as_text=True) → request.get_data()(bytes)。
@app.route('/upload', methods=['POST'])
def upload_xml_flexible():
# 1. 尝试 multipart 文件
xml_file = request.files.get('xml')
if xml_file and xml_file.filename:
xml_file.seek(0)
content = xml_file.read()
# 2. 否则尝试 raw body(application/xml 或 text/xml)
elif request.content_type in ['application/xml', 'text/xml']:
content = request.get_data()
else:
return 'Unsupported Content-Type or missing file', 400
try:
root = ET.fromstring(content)
return f'Parsed tag: {root.tag}', 200
except ET.ParseError as e:
return f'Malformed XML: {e}', 400-
request.content_type是字符串,如"application/xml; charset=utf-8",用in判断比==更鲁棒 -
ET.fromstring()接收 bytes 或 str,而ET.parse()只接受文件类对象或路径 - 不建议在同一个接口混用两种上传方式——增加测试和维护成本;明确约定一种更可靠
XML 解析的安全风险与最小化防护
默认的 xml.etree.ElementTree 存在 XXE(XML External Entity)漏洞,如果用户上传恶意 XML 引用外部 DTD,可能读取服务器本地文件或发起内网请求。
- 禁用 DTD:用
XMLParser显式关闭load_dtd和resolve_entities - 不要用
minidom或lxml默认配置(它们更易开启 XXE) - 如需 DTD 支持,改用白名单机制或专用 XML 安全库(如 defusedxml)
from xml.etree.ElementTree import XMLParser, parse from io import BytesIOparser = XMLParser() parser.parser.UseForeignDTD(False) # 禁用外部 DTD parser.feed(b'
') tree = parse(BytesIO(b' '), parser)
真正麻烦的不是怎么读 XML,而是你是否清楚这次请求到底走的是哪条路径——multipart?raw body?还是被反向代理篡改过 Content-Type?先抓包看清楚 request.headers 和 request.get_data(cache=True) 的原始字节,再决定用哪个分支。










