Flask框架如何处理XML上传 request.files的用法

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")来提取文件。如果你用 fetchcurl 直接 POST 原始 XML 字符串(Content-Type: application/xml),request.files 会为空——这不是 Bug,是协议限制。

常见错误现象:request.files.get('xml') 返回 None,但 request.datarequest.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 ET

app = 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.filesrequest.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_dtdresolve_entities
  • 不要用 minidomlxml 默认配置(它们更易开启 XXE)
  • 如需 DTD 支持,改用白名单机制或专用 XML 安全库(如 defusedxml)
from xml.etree.ElementTree import XMLParser, parse
from io import BytesIO

parser = XMLParser() parser.parser.UseForeignDTD(False) # 禁用外部 DTD parser.feed(b'') tree = parse(BytesIO(b''), parser)

真正麻烦的不是怎么读 XML,而是你是否清楚这次请求到底走的是哪条路径——multipart?raw body?还是被反向代理篡改过 Content-Type?先抓包看清楚 request.headersrequest.get_data(cache=True) 的原始字节,再决定用哪个分支。