上传文件

哦,上传文件可是个经典的好问题了。 文件上传的基本概念实际上非常简单, 他基本是这样工作的: 它基本上这样工作:

  1. 一个 <form> 标签被标记有 enctype=multipart/form-data ,并且 在里面包含一个 <input type=file> 标签。
  2. 应用程序从请求对象上的files字典访问文件。
  3. 使用文件的save()方法将文件永久保存在文件系统的某个位置。

A简介

让我们建立一个非常基础的小应用,这个小应用可以上传文件到一个指定的文件夹里, 然后将这个文件显示给用户。 让我们看看这个应用的基础代码:

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

UPLOAD_FOLDER = '/path/to/the/uploads'
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])

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

所以首先我们需要几个导入。 werkzeug.secure_filename() 将会在稍后进行解释。 UPLOAD_FOLDER是存储上传文件的位置,ALLOWED_EXTENSIONS是允许的文件扩展名。

为什么我们限制上传文件的后缀呢? 您可能不希望您的用户能够上传任何文件 到服务器上,如果服务器直接将数据发送给客户端。 以这种方式,您可以确保 您的用户不能上传可能导致 XSS 问题(参考 跨站脚本攻击(XSS) )的 HTML 文件。 此外,如果服务器执行它们,但是他们的服务器上安装了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 user does not select file, browser also
        # submit a empty part without 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('uploaded_file',
                                    filename=filename))
    return '''
    <!doctype html>
    <title>Upload new File</title>
    <h1>Upload new File</h1>
    <form method=post enctype=multipart/form-data>
      <p><input type=file name=file>
         <input type=submit value=Upload>
    </form>
    '''

那么secure_filename()函数实际上做了什么? 现在的问题 是,有一个信条叫做“永远别相信你用户的输入” ,这句话对于上传文件的文件名也是同样 有效的。 对于上传的文件的文件名也是如此。 所有提交的表单数据都可以伪造,而文件名本身也可能是危险的。 就目前而言: 在将文件保存在文件系统之前,要坚持使用这个函数来确保文件名是安全的。

关于文件名安全的更多信息

所以你感兴趣的是什么secure_filename()函数,如果你不使用它的问题是什么?因此,假设有人会将filename的以下信息发送到您的应用程序:

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

假设../的数量是正确的,并且您将与UPLOAD_FOLDER连接,用户可能有权修改服务器文件系统上的文件,他或她不应该修改。 这么做需要一些关于此应用情况的技术知识,但是相信我, 骇客们都有足够的耐心 :)

现在我们来研究一下这个函数的功能:

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

现在还有最后一件事没有完成: 提供对已上传文件的访问服务。 upload_file()中,我们将用户重定向到url_for('uploaded_file', filename = filename) is,/uploads/filename 否则就将其写入一个临时未知(如函数 tempfile.gettempdir() 返回的路径)。 在 Flask 0.5 以上的版本我们可以使用一个函数来实现此功能:

from flask import send_from_directory

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

或者,您可以将uploaded_file注册为build_only规则,并使用SharedDataMiddleware这也适用于旧版本的Flask:

from werkzeug import SharedDataMiddleware
app.add_url_rule('/uploads/<filename>', 'uploaded_file',
                 build_only=True)
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
    '/uploads':  app.config['UPLOAD_FOLDER']
})

运行应用,不出意外的话,一切都应该像预期那样工作了。

改进上传

0.6 新版功能.

Flask 到底是如何处理上传的呢? 如果文件合理小,否则它会将它们存储在web服务器的内存中,否则在临时位置(由tempfile.gettempdir()返回)。但是如何指定上传中止的最大文件大小? 但是怎么指定一个文件大小的上限,当文件大于此限制,就放弃 上传呢? 默认 Flask 会很欢乐地使用无限制的空间,但是您可以通过在配置中设定 MAX_CONTENT_LENGTH 键的值来限制它:

from flask import Flask, Request

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

上面的代码将会把上传文件限制为最大 16 MB 。 如果传输较大的文件,Flask将产生RequestEntityTooLarge异常。

这个特性是在 Flask 0.6 中被加入的,但是更老的版本也可以通过构建请求对象 的子类来实现。 更多信息请查询 Werkzeug 文档中文件处理部分的内容。

上传进度条

以前,很多开发者实现进度条的方法是这样的: 一边小块小块地读取传输来的文件, 一边将上传进度储存在数据库中,然后在通过客户端的 JavaScript 代码读取进度。 客户端 询问一些他本应该已经知道的事情。 你意识到讽刺吗?客户要求的东西,它应该已经知道。

更简单的解决方案

现在有了一些性能更好、运行更可靠的解决方案。 有像jQuery这样的JavaScript库,它们具有表单插件,以方便构建进度条。

因为文件上传的常见模式在处理上传的所有应用程序中几乎不变,所以还有一个名为Flask-Uploads的Flask扩展,它实现了一个完全成熟的上传机制,扩展和白名单和黑名单等。