flask 文件上传(单文件上传、多文件上传) | 您所在的位置:网站首页 › flask大文件上传 › flask 文件上传(单文件上传、多文件上传) |
文件上传 在HTML中,渲染一个文件上传字段只需要将标签的type属性设为file,即。 这会在浏览器中渲染成一个文件上传字段,单击文件选择按钮会打开文件选择窗口,选择对应的文件后,被选择的文件名会显示在文件选择按钮旁边。 在服务器端,可以和普通数据一样获取上传文件数据并保存。不过需要考虑安全问题,文件上传的漏洞也是比较流行的攻击方式。除了常规的CSRF防范,我们还需要重点关注这几个问题:验证文件类型、验证文件大小、过滤文件名
定义上传表单 在python表单类中创建文件上传字段时,我们使用扩展Flask-WTF提供的FileField类,它集成WTForms提供的上传字段FileField,添加了对Flask的集成。例如: 创建上传表单: from flask_wtf.file import FileField, FileRequired, FileAllowed class UploadForm(FlaskForm): photo = FileField('Upload Image', validators=[FileRequired(), FileAllowed(['jpg','jpeg','png','gif'])]) submit = SubmitField()
在表单类UploadForm()中创建了一个FileField类的photo字段,用来上传图片。 和其他字段类似,需要对文件上传字段进行验证。Flask-WTF在flask_wtf.file模块下提供了两个文件相关的验证器,用法如下:
我们使用FileRequired确保提交的表单字段中包含文件数据。处于安全考虑,必须对上传的文件类型进行限制。如果用户可以上传HTML文件,而且我们同时提供了视图函数获取上传后的文件,那么很容易导致XSS攻击。使用FileAllowed设置允许的文件类型,传入一个包含允许文件类型的后缀名列表。
Flask-WTF提供的FileAllowed是在服务器端验证上传文件,使用HTML5中的accept属性也可以在客户端实现简单的类型过滤。这个属性接收MIME类型字符串或文件格式后缀,多个值之间使用逗号分隔,比如:
当用户单击文件选择按钮后,打开的文件选择窗口会默认将accept属性之外的文件过滤掉(其实没有过滤掉)。 尽管如此,用户还是可以选择设定之外的文件,所以仍然需要在服务器端验证。
验证文件大小,通过设置Flask内置的配置变量MAX_CONTENT_LENGTH,可以显示请求报文的最大长度,单位是字节,比如: app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024
当上传文件的大小超过这个限制后,flask内置的开服务器会中断连接,在生产环境的服务器上会返回413错误响应。
渲染上传表单 在新创建的upload视图里,我们实例化表单类UploadForm,然后传入模板: @app.route('/upload', methods=['GET', 'POST']) def upload(): form = UploadForm() return render_template('upload.html',form = form)
在upload.html中渲染上传表单 {% from 'macros.html' import form_field %} {% extends 'base.html' %} {% block content %} {{ form.csrf_token }} {{ form_field(form.photo) }} {{ form.submit }} {% endblock %}
需要注意的是,当表单中包含文件上传字段时(即type属性为file的input标签)需要将表单的enctype属性设为”multipart/form-data”,这会告诉浏览器将上传数据发送到服务器,否则仅会把文件名作为表单数据提交。
处理上传文件 和普通的表单数据不同,当包含上传文件字段的表单提价后,上传的文件需要在请求对象的files属性(request.files)中获取。这个属性(request.files)是Werkzeug提供的ImmutableMultiDict字典对象,存储字段name键值和文件对象的映射,比如: ImmutableMultiDict([('photo', )])
上传的文件会被Flask解析为Werkzeug中的FileStorage对象(werkzeug.datastructures.FileStorage)。当手动处理时,需要使用文件上传字段的name属性值作为键获取对应的文件对象。比如: request.files.get(‘photo’)
当使用Flask-WTF时,它会自动帮我们获取对应的文件对象,这里我们仍然使用表单类属性的data属性获取上传文件。处理上传表单提交请求的upload视图函数如下: import os app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'uploads') @app.route('/upload', methods=['GET', 'POST']) def upload(): form = UploadForm() if form.validate_on_submit(): f = form.photo.data filename =random_filename(f.filename) f.save(os.path.join(app.config['UPLOAD_PATH'], filename)) flash('Upload success.') session['filenames'] = [filename] return redirect(url_for('show_images')) return render_template('upload.html', form = form)
里面的函数在后面说明 当表单通过验证后,我们通过form.photo.data获取存储上传文件的FileStorage对象。接下来,我们需要处理文件名,通常有三种处理方 处理文件名的方式 1)使用原文件名 如果能够确定文件的来源安全,可以直接使用原文件名,通过FileStorage对象的filename属性获取: filename = f.filename
2)使用过滤后的文件名 如果要支持用户上传文件,我们必须对文件名进行处理,因为攻击者可能会在文件名中加入恶意路径。比如,如果恶意用户在文件名中加入表示上级目录的..(比如../../../home/username/.bashrc或../../etc/passwd),那么当我们保存文件时,如果这里表示上级目录的../数量正确,就会导致服务器上的系统该文件被覆盖或篡改,还有可能执行恶意脚本。我们可以使用Werkzeug提供的secure_filename()函数对文件名进行过滤,传递文件名作为参数,它会过滤掉所有危险字符,返回“安全的文件名”,如下所示:
>>> from werkzeug import secure_filename >>> secure_filename('sam!@$%^&.jpg') 'sam.jpg' >>> secure_filename('sam图片.jpg') 'sam.jpg' >>>
3)统一重命名 secure_filename()函数非常方便,它会过滤掉文件名中的非ASCII字符。但如果文件名完全由非ASCII字符组成,那么会得到一个空文件名: >>> secure_filename('图像.jpg') 'jpg'
为了避免出现这种情况,更好的做法是使用统一的处理方式对所有上传的文件重新命名。随机文件名有很多种方式生成,下面是一个是python内置的uuid模块生成随机文件名的random_filename()函数: import uuid def random_filename(filename): ext = os.path.splitext(filename)[1] new_filename = uuid.uuid4().hex + ext return new_filename其中os.path.splitext()和uuid.uuid4()的用法如下: >>> import os >>> os.path.splitext('d://sam/sam.jpg') ('d://sam/sam', '.jpg')
>>> import uuid >>> uuid.uuid4() UUID('b35f485e-5a79-4d98-8cac-af62be1f0a36') >>> uuid.uuid4().hex '62f65743d16e4b388f9f6eabe3f8e5b4' 这个函数接收原文件名作为参数,使用内置的uuid模块中的uuid4()方法生成新的文件名,并使用hex属性获取十六进制字符串,最后返回包含后缀的新文件名。
UUID(Universally Unique Identifier,通用唯一识别码)是用来表示信息的128位数字,比如用作数据库表的主键。使用标准方法生成的UUID出现重复的可能性接近0。在UUID的标准中,UUID分为5个版本,每个版本使用不同的生产方法并且适用于不同的场景。我们使用的uuid4()方法对应的第4个版本:不接受参数而生成的随机UUID。
在upload视图中,我们调用这个函数获取随机文件名,传入原文件名作为参数: filename = random_filename(f.filename)
处理完文件名后,是时候将文件保存到文件系统中了。在form目录下创建一个uploads文件夹,用于保存上传后的文件。指向这个文件夹的绝对路径存储在自定义配置变量UPLOAD_PATH中: app.config[‘UPLOAD_PATH’] = os.path.join(app.root_path, ‘uploads’) 这里的路径通过app.root_path属性构造,它存储了程序实例所在脚本的绝对路径,相当于: os.path.abspath(os.path.dirname(__file__))。为了保存文件,需要提前手动创建这个文件夹。 调试: print "__file__:",__file__ print "app.root_path:",app.root_path结果: __file__: D:/flask/FLASK_PRACTICE/form/app.py app.root_path: D:\flask\FLASK_PRACTICE\form
对FileStorage对象调用save()方法即可保存,传入包含目标文件夹绝对路径和文件名在内的完整保存路径: f.save(os.path.join(app.config[‘upload_path’], filename)) 文件保存后,我们希望能够显示长传后的图片,为了让上传后的文件能够通过URL获取,我们需要创建一个视图函数来返回上传后的文件,如下所示: @app.route('/uploads/') def get_file(filename): return send_from_directory(app.config['UPLOAD_PATH', filename])
这个视图的作用与Flask内置的static视图类似,通过传入的文件路径返回对应的静态文件。在这个uploads视图中,使用Flask提供的send_from_directory()函数来获取文件,传入文件的路径和文件名作为参数。
在get_file视图的URL规则中,filename变量使用了path转换器以支持传入包含斜线的路径字符串。 upload视图里保存文件后,使用flash()发送一个提示,将文件名保存到session中,最后重定向到show_images视图。show_images视图返回的uploaded.html模板中将从session获取文件名,渲染出上传后的图片。 flash('Upload success.') session['filenames'] = [filename] return redirect(url_for('show_images'))
这里将filename作为列表传入session只是为了兼容下面的多文件上传示例,这两个视图使用同一个模板,使用session可以在模板中统一从session获取文件名列表。
在uploaded.html模板里,我们将传入的文件名作为URL变量,通过上面的get_file视图获取文件URL,作为 |
CopyRight 2018-2019 实验室设备网 版权所有 |