Flask 文件和流


當我們要往客戶端發送大量的數據比較好的方式是使用流,通過流的方式來將響應內容發送給客戶端,實現文件的上傳功能,以及如何獲取上傳后的文件。

響應流的生成

Flask響應流的實現原理就是通過Python的生成器,也就是大家所熟知的yield的表達式,將yield的內容直接發送到客戶端。下面就是一個簡單的實現:

from flask import Flask, Response
 
app = Flask(__name__)
 
@app.route('/large.csv')
def generate_large_csv():
    def generate():
        for row in range(50000):
            line = []
            for col in range(500):
                line.append(str(col))
 
            if row % 1000 == 0:
                print 'row: %d' % row
            yield ','.join(line) + '\n'
 
    return Response(generate(), mimetype='text/csv')

這段代碼會生成一個5萬行100M的csv文件,每一行會通過yield表達式分別發送給客戶端。

運行時你會發現文件行的生成與瀏覽器文件的下載是同時進行的,而不是文件全部生成完畢后再開始下載

這里我們用到了響應類”flask.Response”,它的初始化方法第一個參數就是我們定義的生成器函數,第二個參數指定了響應類型

 

我們將上述方法應用到模板中,如果模板的內容很大,怎么采用流的方式呢?這里我們要自己寫個流式渲染模板的方法。

# 流式渲染模板
def stream_template(template_name, **context):
    # 將app中的請求上下文內容更新至傳入的上下文對象context,
    # 這樣確保請求上下文會傳入即將被渲染的模板中
    app.update_template_context(context)
    # 獲取Jinja2的模板對象
    template = app.jinja_env.get_template(template_name)
    # 獲取流式渲染模板的生成器
    generator = template.stream(context)
    # 啟用緩存,這樣不會每一條都發送,而是緩存滿了再發送
    generator.enable_buffering(5)
 
    return generator

這段代碼的核心,就是通過”app.jinja_env”來訪問Jinja2的Environment對象,然后調用Environment對象的”get_template()”方法來獲得模板對象,再調用模板對象的”stream()”方法生成一個”StreamTemplate”的對象。這個對象實現了”__next__()”方法,可以作為一個生成器使用,如果你看了Jinja2的源碼,你會發現模板對象的”stream()”方法的實現就是使用了yield表達式,所以原理同上例一樣。另外,我們啟用了緩存”enable_buffering()”來避免客戶端發送過於頻繁,其參數的默認值就是5。

現在我們就可以在視圖方法中,采用”stream_template()”,而不是以前介紹的”render_template()”來渲染模板了:

@app.route('/stream.html')
def render_large_template():
    file = open('server.log')
    return Response(stream_template('stream-view.html',logs=file.readlines()))

上例的代碼會將本地的”server.log”日志文件內容傳入模板,並以流的方式渲染在頁面上。

 

”stream_with_context()”方法,它允許生成器在運行期間獲取請求上下文:

from flask import request, stream_with_context
 
@app.route('/method')
def streamed_response():
    def generate():
        yield 'Request method is: '
        yield request.method
        yield '.'
    return Response(stream_with_context(generate()))

因為我們初始化Response對象時調用了”stream_with_context()”方法,所以才能在yield表達式中訪問request對象。

文件上傳

我們分下面4個步驟來實現文件上傳功能:

1、首先建立一個讓用戶上傳文件的頁面,我們將其放在模板”upload.html”中

<!DOCTYPE html>
<title>Upload 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">
 </p> </form>

這里主要就是一個enctype=”multipart/form-data”的form表單;一個類型為file的input框,即文件選擇框;還有一個提交按鈕。

2、定義一個文件合法性檢查函數

# 設置允許上傳的文件類型
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg'])
 
# 檢查文件類型是否合法
def allowed_file(filename):
    # 判斷文件的擴展名是否在配置項ALLOWED_EXTENSIONS中
    return '.' in filename and \
           filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

3、文件提交后,在POST請求的視圖函數中,通過request.files獲取文件對象

這個request.files是一個字典,字典的鍵值就是之前模板中文件選擇框的”name”屬性的值,上例中是”file”;鍵值所對應的內容就是上傳過來的文件對象。

4、檢查文件對象的合法性后,通過文件對象的save()方法將文件保存在本地

我們將第3和第4步都放在視圖函數中,代碼如下:

import os
from flask import flask, render_template
from werkzeug import secure_filename
 
app = Flask(__name__)
# 設置請求內容的大小限制,即限制了上傳文件的大小
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024
 
# 設置上傳文件存放的目錄
UPLOAD_FOLDER = './uploads'
 
@app.route('/upload', 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)
            # 將文件保存在本地UPLOAD_FOLDER目錄下
            file.save(os.path.join(UPLOAD_FOLDER, filename))
            return 'Upload Successfully'
        else:    # 文件不合法
            return 'Upload Failed'
    else:    # GET方法
        return render_template('upload.html')
  • Flask的MAX_CONTENT_LENGTH配置項可以限制請求內容的大小,默認是沒有限制,上例中我們設為5M。
  • 必須調用”werkzeug.secure_filename()”來使文件名安全
  • Flask處理文件上傳的方式:如果上傳的文件很小,那么會把它直接存在內存中。否則就會把它保存到一個臨時目錄下,通過”tempfile.gettempdir()”方法可以獲取這個臨時目錄的位置。

一個簡便的方法來讓用戶獲取已上傳的文件:

from flask import send_from_directory
 
@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(UPLOAD_FOLDER, filename)

這個幫助方法”send_from_directory()”可以安全地將文件發送給客戶端,它還可以接受一個參數”mimetype”來指定文件類型,和參數”as_attachment=True”來添加響應頭”Content-Disposition: attachment”


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM