flask⽂件上传(单⽂件上传、多⽂件上传)--
⽂件上传
在HTML中,渲染⼀个⽂件上传字段只需要将<input>标签的type属性设为file,即<input 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类型字符串或⽂件格式后缀,多个值之间使⽤逗号分隔,
⽐如:
<input type=”file” id=”profile_pic” name=”profile_pic” accept=”.jpg, .jpeg, .png, .gif”>
当⽤户单击⽂件选择按钮后,打开的⽂件选择窗⼝会默认将accept属性之外的⽂件过滤掉(其实没有过滤掉)。
尽管如此,⽤户还是可以选择设定之外的⽂件,所以仍然需要在服务器端验证。
验证⽂件⼤⼩,通过设置Flask内置的配置变量MAX_CONTENT_LENGTH,可以显⽰请求报⽂的最⼤长度,单位是字节,⽐如:
当上传⽂件的⼤⼩超过这个限制后,flask内置的开服务器会中断连接,在⽣产环境的服务器上会返回413错误响应。
渲染上传表单
在新创建的upload视图⾥,我们实例化表单类UploadForm,然后传⼊模板:
@ute('/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 method="post" enctype="multipart/form-data">
{{ form.csrf_token }}
{{ form_field(form.photo) }}<br>
{{ form.submit }}<br>
</form>
{% endblock %}
需要注意的是,当表单中包含⽂件上传字段时(即type属性为file的input标签)需要将表单的enctype属性设为”multipart/form-data”,这会告诉浏览器将上传数据发送到服务器,否则仅会把⽂件名作为表单数据提交。
处理上传⽂件
和普通的表单数据不同,当包含上传⽂件字段的表单提价后,上传的⽂件需要在请求对象的files属性
(request.files)中获取。这个属性(request.files)是Werkzeug提供的ImmutableMultiDict字典对象,存储字段name键值和⽂件对象的映射,⽐如:
ImmutableMultiDict([('photo', <FileStorage: u'xiaxiaoxu.JPG' (image/jpeg)>)])
上传的⽂件会被Flask解析为Werkzeug中的FileStorage对象(werkzeug.datastructures.FileStorage)。当⼿动处理时,需要使⽤⽂件上传字段的name属性值作为键获取对应的⽂件对象。⽐如:
(‘photo’)
当使⽤Flask-WTF时,它会⾃动帮我们获取对应的⽂件对象,这⾥我们仍然使⽤表单类属性的data属性获取上传⽂件。处理上传表单提交请求的upload视图函数如下:
import os
@ute('/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.fig['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中:
这⾥的路径通过_path属性构造,它存储了程序实例所在脚本的绝对路径,相当于:getsavefilename
os.path.abspath(os.path.dirname(__file__))。为了保存⽂件,需要提前⼿动创建这个⽂件夹。
调试:
print"__file__:",__file__
_path:",_path
结果:
__file__: D:/flask/FLASK_PRACTICE/form/app.py
<_path: D:\flask\FLASK_PRACTICE\form
对FileStorage对象调⽤save()⽅法即可保存,传⼊包含⽬标⽂件夹绝对路径和⽂件名在内的完整保存路径:
f.save(os.path.fig[‘upload_path’], filename))
⽂件保存后,我们希望能够显⽰长传后的图⽚,为了让上传后的⽂件能够通过URL获取,我们需要创建⼀个视图函数来返回上传后的⽂件,如下所⽰:
@ute('/uploads/<path:filename>')
def get_file(filename):
return send_from_fig['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,作为<img>标签的src属性值,如下所⽰:
<img src="{{ url_for('get_file', filename=filename) }}">
访问127.0.0.1:5000/upload,打开⽂件上传⽰例,选择⽂件并提交后即可看到上传后的图⽚。另外,在uploads⽂件夹中可以看到上传的⽂件。
提交后,看到图⽚
uploads⽬录下保存的⽂件:
下⾯列⼀下涉及的⽂件:
app.py:
from flask_wtf.file import FileField, FileRequired, FileAllowed
from flask import send_from_directory
class UploadForm(FlaskForm):
photo = FileField('Upload Image', validators=[FileRequired(), FileAllowed(['jpg','jpeg','png','gif'])])
submit = SubmitField()
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论