上传文件

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

  1. 一个``<form>``标签用``enctype = multipart / form-data``标记,``<input type = file>``放在那个表格中。

  2. 应用程序从请求对象上的:attr:`~flask.request.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() 功能真的可以吗?现在的问题是,有一个叫做“从不信任用户输入”的原则。对于上载文件的文件名也是如此。所有提交的表单数据都可以被伪造,文件名也可能是危险的。暂时记住:在将文件直接存储到文件系统之前,一定要使用该函数来保护文件名。

专业人员信息

所以你对以下内容感兴趣:func:`~werkzeug.utils.secure_filename`函数可以做什么,如果你不使用它会出现什么问题? 所以想象一下有人会将以下信息作为`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
)

改善上传

Changelog

0.6 新版功能.

那么Flask到底是如何处理上传的呢?如果文件相当小,它会将它们存储在Web服务器的内存中,否则将存储在临时位置(如 tempfile.gettempdir() ). 但如何指定上载中止后的最大文件大小?默认情况下,Flask可以接受无限内存的文件上传,但是可以通过设置 MAX_CONTENT_LENGTH 配置键:

from flask import Flask, Request

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

上面的代码将允许的最大有效负载限制为16兆字节。 如果传输的文件较大,Flask会引发:exc:`~werkzeug.exceptions.RequestEntityTooLarge`异常。

连接重置问题

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

这个特性被添加到了flask 0.6中,但是在旧版本中也可以通过子类化请求对象来实现。有关更多信息,请参阅有关文件处理的Werkzeug文档。

上载进度条

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

更简单的解决方案

现在有更好的解决方案可以更快、更可靠地工作。有一些像jquery_u这样的javascript库有表单插件来简化进度条的构建。

因为文件上载的通用模式在处理上载的所有应用程序中几乎都是不变的,所以还有一个称为 Flask-Uploads 它实现了一个完整的上载机制,允许控制哪些文件扩展名可以上载。