欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Flask上传文件

程序员文章站 2022-07-15 13:09:51
...

Flask上传文件

文件上传的基本原理实际上很简单,基 本上是:

  1. 一个带有 enctype=multipart/form-data<form> 标记,标记中含有 一个 <input type=file>
  2. 应用通过请求对象的 files 字典来访问文件。
  3. 使用文件的 save() 方法把文件 永久地保存在文件系统中。

例,上传文件html页:

<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <input type="file" name="img">
    <input type="submit" value="上传">
</form>

让我们从一个基本的应用开始,这个应用上传文件到一个指定目录,并把文件显示给 用户。

以下是应用的前导代码:

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

# 为了便于迁移,上传文件的路径使用了os模块来寻找当前文件夹拼接windows文件分隔符再拼接真正的目录名,
# 例如我的就是uploads,最后再加上一个文件分隔符即可。
UPLOAD_FOLDER =os.path.curdir+os.path.sep+'uploads'+os.path.sep
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 是允许上传的文件扩展名的集合。

注:为了便于迁移,上传文件的路径我使用了os模块来寻找当前文件夹拼接windows文件分隔符再拼接真正的目录名。例如我的就是uploads,最后再加上一个文件分隔符即可。

python中os.path常用模块:

os.path.sep -->windows路径分隔符 
os.path.altsep -->linux下就用:'/' 

根目录:os.path.curdir
当前目录:os.path.pardir
父目录:os.path.abspath(path)
绝对路径:os.path.join()

常用来链接路径:os.path.split(path),把path分为目录和文件两个部分,以列表返回。
  • 为什么要限制文件的扩展名呢?

如果直接向客户端发送数据,那么你可能不会想让用户上传任意文件。否则,你必须确保用户不能上传 HTML 文件,因为 HTML 可能引 起 XSS 问题(参见 跨站脚本攻击(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 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))
           # 返回反向解析得到的URL
           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>
      <input type=file name=file>
      <input type=submit value=Upload>
    </form>
    '''

那么 secure_filename() 函数到底是有什么用?

有一条原则是“永远不要信任用户输入”

这条原则同样适用于已上传文件的文件名。所有提交的表单数据可能是伪造的,文件名也可以是危险的。此时要谨记:在把文件保存到文件系统之前总是要使用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) ,即 /uploads/filename

因此我们写一个 uploaded_file() 来返回该文件 名称。

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']
})

如果你现在运行应用,那么应该一切都应该按预期正常工作。

Flask上传文件

要注意的是,目前应用下,你上传的图片文件名要为英文。中文的话,会出现弹框问你保存还是打开……

改进上传

Flask 到底是如何处理文件上传的呢?

  • 如果上传的文件很小,那么会把它们储存在内存中。

  • 否则就会把它们保存到一个临时的位置(通过 tempfile.gettempdir() 可以得到这个位置)。

如何限制上传文件的尺寸呢?

缺省情况下, Flask 是不限制上传文件的尺寸的。可以通过设置配置的 MAX_CONTENT_LENGTH 来限制文 件尺寸:

from flask import Flask, Request

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

上面的代码会把尺寸限制为 16 M 。如果上传了大于这个尺寸的文件, Flask 会抛 出一个 RequestEntityTooLarge 异常。

连接重置问题

当使用本地开发服务器时,可能会得到一个连接重置,而不是一个 413 响应。 在生产 WSGI 服务器上运行应用时会得到正确的响应。

Flask 0.6 版本中添加了这个功能。但是通过继承请求对象,在较老的版本中也可以实现这个功能。更多信息请参阅 Werkzeug 关于文件处理的文档。

上传进度条

在不久以前,许多开发者是这样实现上传进度条的:分块读取上传的文件,在数据库 中储存上传的进度,然后在客户端通过 JavaScript 获取进度。简而言之,客户端每 5 秒钟向服务器询问一次上传进度。觉得讽刺吗?客户端在明知故问。

一个更简便的方案

现在有了更好的解决方案,更快且更可靠。像 jQuery 之类的 JavaScript 库包含的轻松构建进度条的插件。

因为所有应用中上传文件的方案基本相同,因此可以使用 Flask-Uploads 扩展来实现文件上传。这个扩展实现了完整的上传机制,还具有白名单功能、黑名单功能以及其他功能。

文:铁乐与猫

2018-9-6

参考引用

Flask官网中文文档-上传文件

相关标签: flask