Flask 上傳文件

2021-08-11 10:07 更新

哦,上傳文件可是個(gè)經(jīng)典的好問(wèn)題了。文件上傳的基本概念實(shí)際上非常簡(jiǎn)單, 他基本是這樣工作的:

  1. 一個(gè) <form> 標(biāo)簽被標(biāo)記有 enctype=multipart/form-data ,并且 在里面包含一個(gè) <input type=file> 標(biāo)簽。
  2. 服務(wù)端應(yīng)用通過(guò)請(qǐng)求對(duì)象上的 files 字典訪問(wèn)文件。
  3. 使用文件的 save() 方法將文件永久地 保存在文件系統(tǒng)上的某處。

一點(diǎn)點(diǎn)介紹

讓我們建立一個(gè)非?;A(chǔ)的小應(yīng)用,這個(gè)小應(yīng)用可以上傳文件到一個(gè)指定的文件夾里, 然后將這個(gè)文件顯示給用戶。讓我們看看這個(gè)應(yīng)用的基礎(chǔ)代碼:

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

首先我們導(dǎo)入一些東西,大多數(shù)內(nèi)容都是直接而容易的。werkzeug.secure_filename() 將會(huì)在稍后進(jìn)行解釋。 UPLOAD_FOLDER 是我們儲(chǔ)存上傳的文件的地方,而 ALLOWED_EXTENSIONS 則是允許的文件類型的集合。然后我們手動(dòng)為應(yīng)用添加一個(gè)的 URL 規(guī)則。我們 通常很少這樣做,但是為什么這里要如此呢?原因是我們希望實(shí)際部署的服務(wù)器 (或者我們的開(kāi)發(fā)服務(wù)器)來(lái)為我們提供這些文件的訪問(wèn)服務(wù),所以我們只需要 一個(gè)規(guī)則用來(lái)生成指向這些文件的 URL 。

為什么我們限制上傳文件的后綴呢?您可能不希望您的用戶能夠上傳任何文件 到服務(wù)器上,如果服務(wù)器直接將數(shù)據(jù)發(fā)送給客戶端。以這種方式,您可以確保 您的用戶不能上傳可能導(dǎo)致 XSS 問(wèn)題(參考 跨站腳本攻擊(XSS) )的 HTML 文件。也 確保會(huì)阻止 .php 文件以防其會(huì)被運(yùn)行。當(dāng)然,誰(shuí)還會(huì)在服務(wù)器上安裝 PHP 啊,是不是? :)

下一步,就是檢查文件類型是否有效、上傳通過(guò)檢查的文件、以及將用戶重定向到 已經(jīng)上傳好的文件 URL 處的函數(shù)了:

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 new File</h1>
    <form action="" method=post enctype=multipart/form-data>
      <p><input type=file name=file>
         <input type=submit value=Upload>
    </form>
    '''

那么 secure_filename() 函數(shù)具體做了那些事呢?現(xiàn)在的問(wèn)題 是,有一個(gè)信條叫做“永遠(yuǎn)別相信你用戶的輸入” ,這句話對(duì)于上傳文件的文件名也是同樣 有效的。所有提交的表單數(shù)據(jù)都可以偽造,而文件名本身也可能是危險(xiǎn)的。在攝氏只需記住: 在將文件保存在文件系統(tǒng)之前,要堅(jiān)持使用這個(gè)函數(shù)來(lái)確保文件名是安全的。

關(guān)于文件名安全的更多信息

您對(duì) secure_filename() 的具體工作和您沒(méi)使用它會(huì)造成的后果 感興趣?試想一個(gè)人可以發(fā)送下列信息作為 filename 給您的應(yīng)用:

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

假定 ../ 的數(shù)量是正確的,而您會(huì)將這串字符與 UPLOAD_FOLDER 所指定的 路徑相連接,那么這個(gè)用戶就可能有能力修改服務(wù)器文件系統(tǒng)上的一個(gè)文件,而他 不應(yīng)該擁有這種權(quán)限。這么做需要一些關(guān)于此應(yīng)用情況的技術(shù)知識(shí),但是相信我, 駭客們都有足夠的耐心 :)

現(xiàn)在我們來(lái)研究一下這個(gè)函數(shù)的功能:

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

現(xiàn)在還有最后一件事沒(méi)有完成: 提供對(duì)已上傳文件的訪問(wèn)服務(wù)。 在 Flask 0.5 以上的版本我們可以使用一個(gè)函數(shù)來(lái)實(shí)現(xiàn)此功能:

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 注冊(cè) build_only 規(guī)則,然后使用 SharedDataMiddleware 類來(lái)實(shí)現(xiàn)下載服務(wù)。這種方法 同時(shí)支持更老版本的 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']
})

運(yùn)行應(yīng)用,不出意外的話,一切都應(yīng)該像預(yù)期那樣工作了。

改進(jìn)上傳功能

0.6 新版功能.

Flask 到底是如何處理上傳的呢?如果服務(wù)器相對(duì)較小,那么他會(huì)先將文件儲(chǔ)存在 網(wǎng)頁(yè)服務(wù)器的內(nèi)存當(dāng)中。否則就將其寫(xiě)入一個(gè)臨時(shí)未知(如函數(shù) tempfile.gettempdir() 返回的路徑)。但是怎么指定一個(gè)文件大小的上限,當(dāng)文件大于此限制,就放棄 上傳呢? 默認(rèn) Flask 會(huì)很歡樂(lè)地使用無(wú)限制的空間,但是您可以通過(guò)在配置中設(shè)定 MAX_CONTENT_LENGTH 鍵的值來(lái)限制它:

from flask import Flask, Request

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024

上面的代碼將會(huì)把上傳文件限制為最大 16 MB 。 如果請(qǐng)求傳輸一個(gè)更大的文件, Flask 會(huì)拋出一個(gè) RequestEntityTooLarge 異常。

這個(gè)特性是在 Flask 0.6 中被加入的,但是更老的版本也可以通過(guò)構(gòu)建請(qǐng)求對(duì)象 的子類來(lái)實(shí)現(xiàn)。更多信息請(qǐng)查詢 Werkzeug 文檔中文件處理部分的內(nèi)容。

上傳進(jìn)度條

以前,很多開(kāi)發(fā)者實(shí)現(xiàn)進(jìn)度條的方法是這樣的: 一邊小塊小塊地讀取傳輸來(lái)的文件, 一邊將上傳進(jìn)度儲(chǔ)存在數(shù)據(jù)庫(kù)中,然后在通過(guò)客戶端的 JavaScript 代碼讀取進(jìn)度。 簡(jiǎn)單來(lái)說(shuō),客戶端會(huì)每5秒鐘詢問(wèn)服務(wù)器傳輸?shù)倪M(jìn)度。您感覺(jué)到這種諷刺了么?客戶端 詢問(wèn)一些他本應(yīng)該已經(jīng)知道的事情。

現(xiàn)在有了一些性能更好、運(yùn)行更可靠的解決方案。WEB 已經(jīng)有了不少變化,現(xiàn)在您可以 使用 HTML5、Java、Silverlight 或者 Flash 來(lái)實(shí)現(xiàn)客戶端更好的上傳體驗(yàn)??匆豢?下面列出的庫(kù)的連接,可以找到一些很好的樣例。

更簡(jiǎn)單解決方案

因?yàn)榇嬖谝粋€(gè)處理上傳文件的范式,這個(gè)范式在大多數(shù)應(yīng)用中機(jī)會(huì)不會(huì)有太大改變, 所以 Flask 存在一個(gè)擴(kuò)展名為 Flask-Uploads ,這個(gè)擴(kuò)展實(shí)現(xiàn)了一整套成熟的 文件上傳架構(gòu)。它提供了包括文件類型白名單、黑名單等多種功能。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)