1. 程式人生 > >flask如何使模板返回大文件,又不消耗大量內存

flask如何使模板返回大文件,又不消耗大量內存

syntax rip mini generator 當我 n-k range body -m

當我們要往客戶端發送大量的數據,比如一個大文件時,將它保存在內存中再一次性發到客戶端開銷很大。比較好的方式是使用流,本篇就要介紹怎麽在Flask中通過流的方式來將響應內容發送給客戶端。此外,我們還會演示如何實現文件的上傳功能,以及如何獲取上傳後的文件。

響應流的生成

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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”,它是”werkzeug.wrappers.Response”類的一個包裝,它的初始化方法第一個參數就是我們定義的生成器函數,第二個參數指定了響應類型。

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

1 2 3 4 5 6 7 8 9 10 11 12 13 # 流式渲染模板 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對象,這個我們在Jinja2系列中有介紹,然後調用Environment對象的”get_template()”方法來獲得模板對象,再調用模板對象的”stream()”方法生成一個”StreamTemplate”的對象。這個對象實現了”__next__()”方法,可以作為一個生成器使用,如果你看了Jinja2的源碼,你會發現模板對象的”stream()”方法的實現就是使用了yield表達式,所以原理同上例一樣。另外,我們啟用了緩存”enable_buffering()”來避免客戶端發送過於頻繁,其參數的默認值就是5。

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

1 2 3 4 5 @app.route(‘/stream.html‘) def render_large_template(): file = open(‘server.log‘) return Response(stream_template(‘stream-view.html‘, logs=file.readlines()))

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

另外註意,在生成器中是無法訪問請求上下文的。不過Flask從版本0.9開始提供了”stream_with_context()”方法,它允許生成器在運行期間獲取請求上下文:

1 2 3 4 5 6 7 8 9 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對象。

轉載:

1、http://www.bjhee.com/flask-ad5.html

flask如何使模板返回大文件,又不消耗大量內存