當咱們要往客戶端發送大量的數據比較好的方式是使用流,經過流的方式來將響應內容發送給客戶端,實現文件的上傳功能,以及如何獲取上傳後的文件。html
Flask響應流的實現原理就是經過Python的生成器,也就是你們所熟知的yield的表達式,將yield的內容直接發送到客戶端。下面就是一個簡單的實現:flask
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」,它的初始化方法第一個參數就是咱們定義的生成器函數,第二個參數指定了響應類型安全
咱們將上述方法應用到模板中,若是模板的內容很大,怎麼採用流的方式呢?這裏咱們要本身寫個流式渲染模板的方法。app
# 流式渲染模板 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()」來渲染模板了:post
@app.route('/stream.html') def render_large_template(): file = open('server.log') return Response(stream_template('stream-view.html',logs=file.readlines()))
上例的代碼會將本地的」server.log」日誌文件內容傳入模板,並以流的方式渲染在頁面上。spa
」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個步驟來實現文件上傳功能:
一、首先創建一個讓用戶上傳文件的頁面,咱們將其放在模板」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框,即文件選擇框;還有一個提交按鈕。
二、定義一個文件合法性檢查函數
# 設置容許上傳的文件類型 ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg']) # 檢查文件類型是否合法 def allowed_file(filename): # 判斷文件的擴展名是否在配置項ALLOWED_EXTENSIONS中 return '.' in filename and \ filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
三、文件提交後,在POST請求的視圖函數中,經過request.files獲取文件對象
這個request.files是一個字典,字典的鍵值就是以前模板中文件選擇框的」name」屬性的值,上例中是」file」;鍵值所對應的內容就是上傳過來的文件對象。
四、檢查文件對象的合法性後,經過文件對象的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')
一個簡便的方法來讓用戶獲取已上傳的文件:
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」