文件上传的老问题,文件上传的基本思想是相当简单,它基本的工作流程大概是这样的:
让我们启动一个非常基础的应用来实现上传一个文件到指定的路径下,并且为用户展示这个文件,在开始之前,需要安装这2个模块
pip install flask flask_uploads
下面是这个应用的基础代码:
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 = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
解释:首先我们需要导入几个模块,大多数是直截了当的,werkzeug.secure_filename()会在下面解释,UPLOAD_FOLDER是我们将要上传文件的存储路径,ALLOWED_EXTENSIONS指定了我们上传的文件的格式扩展名,不在扩展名里面的将会被拒绝
那我们为什么要限制文件的扩展名呢?在服务端直接发送数据到客户端的情况下,你可能不希望你的用户能够上传所有的东西;通过限制扩展名,你可以确保会引起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 an 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>
<input type=file name=file>
<input type=submit value=Upload>
</form>
'''
secure_filename()函数实际是做什么的?这个问题有个原则就是”永远不相信用户的输入“, 对于一个上传文件的名字来说,这通常是真实的;所有提交的表单数据都可以被伪造,文件名也可以变得危险;只要记住:在存储文件名到文件系统之前,始终使用该函数来保存文件名
若你对secure_filename()函数的功能和比较感兴趣以及当我们不使用这个函数时么会产生什么问题;想象有人想给你发送下面的一些信息作为文件名到你的应用上:
>>> filename = "../../../../home/username/.bashrc"
假设../的数目是正确的,并且你会加入这个路径中,在服务端的文件系统上,用户也许有能力去验证这个文件,或者不会验证,这需要掌握这个应用的一些知识,但是相信我,黑客会有耐心
现在让我们看下这个函数是怎样工作的:
>>> 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如何精确的控制上传?如果文件是合理大小的,它将会被存储在web服务器的内存中换言之就是一个临时存储位置(通过tempfile.gettempdir()函数获取),当一个上传意外中止之后,我们该如何指定文件的大小呢?默认情况下,flask将愉快的上传文件到无限量的内存中,但是可以通过配置MAX_CONTENT_LENGTH来限制:
from flask import Flask, Request
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
上面的代码将会限制上传的最大允许载荷到16M,如果传输更大的文件,flask将会抛出一个RequestEntityTooLarge的异常
!!!连接重置的问题
在使用本地开发服务器时,你或许会得到一个connection reset error代替413应答,当你通过 WSGI server来运行app时,会得到正确的回复
Flask 0.6中加入了这个特性,但是在对请求对象进行子类分类时,即使是老的版本也可能出现这个问题。