2011年11月

上传文件的最简单的一个解决方案其实确实很简单,就下面这几个步骤:

  1. 一个被标记为 enctype="multipart/form-data" 且包含一个 <input type="file| /> 标签的表单(<form> )。
  2. 程序可以从 request.files 中直接访问已上传的文件。
  3. 通过 save() 函数将已上传的文件从临时文件存放位置移动到我们希望它存放的位置。

简单的开始

让我们从一个最简单的上传文件的应用来始,在这个应用中,用户上传的文件会被存放在一个特别的文件目录中,显示会显示该文件给用户,先让我们看看整个应用的引导程序:

import os
from flask import Flask, request, redirect, url_for
from werkzeug 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 则指定了允许上传的文件类型,但是你可以看到我并没有什么 一个URL规则来指定如何访问文件,因为在通常情况,使用服务器软件来服务这些软件比我们用程序发送效率更高,我们只需要在程序里面指定文件的URL路径即可。

至于为什么我们会限定上传的文件类型,这个原因很简单吧,你总不会想让你的用户可以存放任何文件到你的服务器上吧?如果你允许用户上传任何类型文件,那么这之间的风险将很大,比如他们可以上传一个可执行的脚本到你的服务器上,而如果这个脚本还正好处于一个可执行的目录中的话,那么肯定是很不安全的了。

接下面我们将来检查上传至服务器的文件的格式,如果文件允许被上传,那么我们会指定将用户页面转向至刚刚上传的文件上。

def allowed_file(filename):
return '.' in filename and filename.rsplit('.',1)[1] in ALLOWED_EXTENSIONS
@app.route('/', methods = ['GET','POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file']
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 File</h1>
<form action="/" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
'''

一些给专家们的建议

在上面的代码中, secure_filename() 到底做了什么呢?我们先从一个最觉提起的问题上来说:“永远不要相信用户的输入”,你就想象一下,如果某一个用户上传了有下面这样文件名的文件至你的服务器:

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

然后再想象一下,如果 ../ 的个数正好,那么,将这个文件名的文件与你的上传路径合并之后,他将有可能修改你的服务器的文件,当然,这种情况很少能出现,而且一般的用户也很少会恶意的去这么做,但是相信我,专门这样做的黑客如果想要黑你,那么他一定会想尽办法这么去做,所以,还是那句话:“永远不要相信任何用户的输入”。

我们从Python命令行中来看一下下这个函数是如何工作的:

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

现在只剩下最后一件事情了,将用户上传的文件发送给用户查看,在 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 ShareDataMiddleware
app.add_url_rule('/uploads/<filename>', 'uploaded_file', build_only = True)
app.wsgi_app = ShareDataMiddleware(app.wsgi_app, {'uploads': app.config['UPLOAD_FOLDER'] })

现在你运行你的程序,应该是可以实现文件上传,检测文件类型以及文件名,输出文件给用户查看的所有功能了。

更加高级的文件上传

那么Flask到底是怎么样处理文件上传的呢?是这样的,当用户上传文件至服务器时,如果文件小的话(这个在不同系统上面是不一样的量),Flask会将其保存在内存中,或者如果文件很大的话,会被保存到一个临时文件中(可以使用 tempfile.gettempdir()获取到),默认情况下,只要服务器内存允许,Flask不会管用户上传的文件有多大,但是我们很多时候并不希望这样,这时,我们可以使用通过设置 app 的 MAX_CONTENT_LENGTH 参数来限定用户上传的文件最大大小:

from flask import Flask, Request
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024

上面的设置将限定用户最大只允许上传不超过 16Mb的文件,如果用户上传的文件过大,刚Flask会抛出一个 RequestEntityTooLarge 异常。