• 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) + '
    '
     
        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”

  • 相关阅读:
    Android AsyncTask
    android 自定义 view 和 ViewGroup
    Android Acitivity 生命周期
    Android Service 与 IntentService
    Android LocalBroadcastManager 与 BroadcastReceiver
    如何提升 service 等级,不被kill(整合)
    Android 插件开发,做成动态加载
    新提交审核app保留检查更新入口将被拒绝(读取App Store 版本号的)
    Android: Service中创建窗口显示
    如何升级PowerShell
  • 原文地址:https://www.cnblogs.com/Erick-L/p/7015783.html
Copyright © 2020-2023  润新知