html5文件移到flask,Flask进阶系列(五)–文件和流

当我们要往客户端发送大量的数据,比如一个大文件时,将它保存在内存中再一次性发到客户端开销很大。比较好的方式是使用流,本篇就要介绍怎么在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,它是werkzeug.wrappers.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”对象,这个我们在Jinja2系列中有介绍,然后调用”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”日志文件内容传入模板,并以流的方式渲染在页面上。

另外注意,在生成器中是无法访问请求上下文的。不过Flask从版本0.9开始提供了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”中

Upload File

Upload new File

这里主要就是一个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

这样避免用户上传脚本文件如js或php文件,来干坏事!

文件提交后,在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')

解释都写在注释里了,应该很容易看懂。重点要介绍的就是:

Flask的MAX_CONTENT_LENGTH配置项可以限制请求内容的大小

默认是没有限制,上例中我们设为5M。

必须调用werkzeug.secure_filename()来使文件名安全

比如用户上传的文件名为”../../../../home/username/.bashrc”,secure_filename()方法可以将其转为”homeusername.bashrc”。还是用来避免用户干坏事!

Flask处理文件上传的方式

如果上传的文件很小,那么会把它直接存在内存中。否则就会把它保存到一个临时目录下,通过tempfile.gettempdir()方法可以获取这个临时目录的位置。

另外Flask从0.5版本开始提供了一个简便的方法来让用户获取已上传的文件:

from flask import send_from_directory

@app.route('/uploads/')

def uploaded_file(filename):

return send_from_directory(UPLOAD_FOLDER, filename)

这个帮助方法send_from_directory()可以安全地将文件发送给客户端,它还可以接受一个参数mimetype来指定文件类型,和参数as_attachment=True来添加响应头Content-Disposition: attachment。

本篇中的示例代码可以在这里下载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值