Flask上傳文件
文件上傳的基本原理實際上很簡單,基 本上是:
- 一個帶有
enctype=multipart/form-data
的<form>
標記,標記中含有 一個<input type=file>
。 - 應用通過請求對象的
files
字典來訪問文件。 - 使用文件的
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 到底是如何處理文件上傳的呢?
-
如果上傳的文件很小,那么會把它們儲存在內存中。
-
否則就會把它們保存到一個臨時的位置(通過
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
參考引用