上传文件 — Flask 文档

来自菜鸟教程
Flask/docs/2.0.x/patterns/fileuploads
跳转至:导航、​搜索

上传文件

啊,是的,文件上传的老问题。 文件上传的基本思想其实很简单。 它基本上是这样工作的:

  1. <form> 标签用 enctype=multipart/form-data 标记,<input type=file> 以该形式放置。
  2. 应用程序从请求对象上的 files 字典访问文件。
  3. 使用文件的 save() 方法将文件永久保存在文件系统的某处。

温柔的介绍

让我们从一个非常基本的应用程序开始,该应用程序将文件上传到特定的上传文件夹并向用户显示文件。 让我们看看我们的应用程序的引导代码:

import os
from flask import Flask, flash, request, redirect, url_for
from werkzeug.utils import secure_filename

UPLOAD_FOLDER = '/path/to/the/uploads'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

所以首先我们需要几个进口。 大多数应该很简单,werkzeug.secure_filename() 稍后解释。 UPLOAD_FOLDER 是我们存储上传文件的地方,ALLOWED_EXTENSIONS 是允许的文件扩展名集。

为什么我们要限制允许的扩展名? 如果服务器直接将数据发送到客户端,您可能不希望您的用户能够将所有内容上传到那里。 这样您就可以确保用户无法上传会导致 XSS 问题的 HTML 文件(请参阅 跨站点脚本 (XSS) )。 如果服务器执行 .php 文件,还要确保禁止它们,但是谁在他们的服务器上安装了 PHP,对吧? :)

接下来是检查扩展名是否有效以及上传文件并将用户重定向到上传文件的 URL 的函数:

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        # check if the post request has the file part
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']
        # If the user does not select a file, the browser submits an
        # empty file without a filename.
        if file.filename == '':
            flash('No selected file')
            return redirect(request.url)
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            return redirect(url_for('download_file', name=filename))
    return '''
    <!doctype html>
    <title>Upload new File</title>
    <h1>Upload new File</h1>
    <form method=post enctype=multipart/form-data>
      <input type=file name=file>
      <input type=submit value=Upload>
    </form>
    '''

那么 secure_filename() 函数实际上做了什么? 现在的问题是有一个叫做“永远不要相信用户输入”的原则。 对于上传文件的文件名也是如此。 所有提交的表单数据都可能被伪造,文件名也可能是危险的。 目前请记住:在将文件名直接存储到文件系统之前,始终使用该函数来保护文件名。

专业人士的信息

所以您对 secure_filename() 函数的作用以及如果您不使用它会出现什么问题感兴趣? 因此,想象一下有人会将以下信息作为 文件名 发送到您的应用程序:

filename = "../../../../home/username/.bashrc"

假设 ../ 的数量是正确的,并且您将其与 UPLOAD_FOLDER 结合起来,用户可能有能力修改他或她不应修改的服务器文件系统上的文件。 这确实需要一些关于应用程序外观的知识,但相信我,黑客很有耐心:)

现在让我们看看这个函数是如何工作的:

>>> secure_filename('../../../../home/username/.bashrc')
'home_username_.bashrc'

我们希望能够提供上传的文件,以便用户可以下载它们。 我们将定义一个 download_file 视图来按名称提供上传文件夹中的文件。 url_for("download_file", name=name) 生成下载 URL。

from flask import send_from_directory

@app.route('/uploads/<name>')
def download_file(name):
    return send_from_directory(app.config["UPLOAD_FOLDER"], name)

如果您使用中间件或 HTTP 服务器来提供文件,您可以将 download_file 端点注册为 build_only,这样 url_for 将在没有视图功能的情况下工作。

app.add_url_rule(
    "/uploads/<name>", endpoint="download_file", build_only=True
)

改进上传

0.6 版中的新功能。


那么 Flask 究竟是如何处理上传的呢? 如果文件相当小,它会将它们存储在网络服务器的内存中,否则存储在临时位置(由 tempfile.gettempdir() 返回)。 但是,您如何指定上传中止后的最大文件大小? 默认情况下,Flask 很乐意接受具有无限内存量的文件上传,但您可以通过设置 MAX_CONTENT_LENGTH 配置键来限制:

from flask import Flask, Request

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000

上面的代码将允许的最大有效负载限制为 16 兆字节。 如果传输更大的文件,Flask 将引发 RequestEntityTooLarge 异常。

连接重置问题

使用本地开发服务器时,您可能会收到连接重置错误而不是 413 响应。 使用生产 WSGI 服务器运行应用程序时,您将获得正确的状态响应。


此功能是在 Flask 0.6 中添加的,但在旧版本中也可以通过对请求对象进行子类化来实现。 有关更多信息,请参阅有关文件处理的 Werkzeug 文档。


上传进度条

不久前,许多开发人员有想法以小块读取传入文件并将上传进度存储在数据库中,以便能够从客户端使用 JavaScript 轮询进度。 客户端每 5 秒询问服务器它传输了多少,但这是它应该已经知道的事情。


更简单的解决方案

现在有更好的解决方案,工作速度更快,更可靠。 有像 jQuery 这样的 JavaScript 库,它们有表单插件来简化进度条的构建。

因为文件上传的通用模式在所有处理上传的应用程序中几乎没有改变,所以也有一些 Flask 扩展实现了一个完整的上传机制,允许控制允许上传哪些文件扩展名。