V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
banxi1988
V2EX  ›  Python

使用 iframe 七牛 Flask 处理文件异步上传

  •  1
     
  •   banxi1988 ·
    banxi1988 · 2015-10-28 22:54:39 +08:00 · 4783 次点击
    这是一个创建于 3313 天前的主题,其中的信息可能已经有所发展或是发生改变。

    0x0 背景

    使用 iframe 上传是很传统的异步上传的方式。我之前从来没有用过。
    最近遇到一个问题是,有用户抱怨说图片上传不了。当然我测试是没有问题的。
    之前的代码是同事基于 七牛的异步上传示例的代码修改而来的,我接着用了。
    由于上传跟后台交互并不多,用户也没有提供更多的信息。为了快速简单的解决这个问题,我干脆使用比较传统的上传方式算了。

    0x1 基于 iframe 的异步上传

    我们知道如果直接提交一个表单,当请求结果返回的时候,当前页面的内容会被响应内容替换掉。
    简单来说,就是页面刷新了。
    我们希望对此进行改进,希望上传操作在后台进行,然后上传完成之后通知我们。

    基于 iframe 的异步上传,我认为关键在于设置 target 属性,
    <form> 的请求响应结果显示到指定的 <iframe> 中。

    查看 <form>target 属性的文档 有如下说明:

    A name or keyword indicating where to display the response that is received after submitting the form.
    iframename: The response is displayed in a named <iframe>.

    我们可以通过设置 <form>target 属性。将其提交结果显示到指定的 iframe中。而不会刷新当前页面。
    也就给我们感觉是在后台进行。(当然 target 属性还有更多其他属性,特别是在 HTML 5 中。)

    上面解决了后台上传的问题,另一个问题就是上传完成的通知了。
    我们什么时候获得上传完成的响应结果?
    一般的做法是,上传完成之后,响应的内容返回一段 JS ,然后其中的 JS 回调指定的处理函数 。

    于是得到如下相关 前端 代码

    <form method="post"  id="tr-upload-form" action="/awards/api/upload_file" enctype="multipart/form-data" target='tr_upload_frame'>
                    <input type="file" name="file" accept="image/jpeg,image/gif,image/png" class="form-control">
                    <input type="submit"  class="btn btn-primary form-control" value='上传'>
                </form>
                <iframe style="display:none" name="tr_upload_frame"></iframe>
    

    下面的处理表单提交的 JS ,当用户点击上传时,动态替换 <form>action 属性,加上回调参数 。

    var $_tr_upload_form = $('#tr-upload-form');
        $_tr_upload_form.on('submit',function(){
            var seed = Math.floor(Math.random() * 1000);
            var callback = 'upload_cb_'+seed;
            var url =  "/awards/api/upload_file";
            $_tr_upload_form.attr('action', url+'?callback='+callback);
            window[callback] = function(data){
                console.log('received callback:',data);
                $_tr_upload_form.attr('action',url);
                delete window[callback];
                if(data.ok){
                    form.avatar = data.key;
                    alert("上传成功")
                }else{
                    alert("上传失败");
                }
            };
        });
    

    0x3 后台上传代码

    后台处理上传,我是用 Flask 和 七牛 Python-SDK-6
    Flask 的 file 属性的 FileStorage 的 stream 实例可以直接用于上传这点很方便。
    响应的结果就是一段带 JS 回调代码的脚本。

    @app.route('/api/upload_file',methods=['POST'])
    def api_upload_file():
        from qiniu.io import  PutExtra,put
        import uuid
    
        callback = request.args.get('callback')
        file = request.files['file']
        uptoken = _create_uptoken()
        key = uuid.uuid4().hex
        extra = PutExtra()
        extra.mime_type = file.mimetype
        ret,err = put(uptoken,key,file.stream,extra)
        ok = True
        if err is not None:
            logger.error("uploa_file error "+str(err))
            ok = False
        data = { "key":key, "ok":ok }
        text =  '''
        <script>
            window.top.window['{callback}']({data});
        </script>
        '''.format(callback=callback,data=json.dumps(data))
        return text
    

    参考

    1. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form.html#attr-target
    2. http://www.ruanyifeng.com/blog/2012/08/file_upload.html
    6 条回复    2015-10-29 10:31:08 +08:00
    onlyxuyang
        1
    onlyxuyang  
       2015-10-28 23:37:33 +08:00 via Android
    七牛太贵了 不是商业应用根本搞不起
    bdbai
        2
    bdbai  
       2015-10-28 23:44:33 +08:00 via iPhone
    @onlyxuyang 算上免费额度的话,小微级应用还是可以接受的,毕竟他们服务很棒。
    5thcat
        3
    5thcat  
       2015-10-29 00:36:10 +08:00
    FormData + ajax 不行吗?要兼容老浏览器?
    typcn
        4
    typcn  
       2015-10-29 01:34:03 +08:00
    什么年代了。。。。。。。。你的用户用的是 IE6 么。。。
    verytoex
        5
    verytoex  
       2015-10-29 09:56:00 +08:00
    @onlyxuyang 免费版好像有 10G 流量,小站基本够用
    banxi1988
        6
    banxi1988  
    OP
       2015-10-29 10:31:08 +08:00
    @5thcat
    FormData + ajax IE 10 才有的。

    @typcn 确实还有 IE 6 , 7 的用户。虽然只有几十个用户。


    @bdbai
    @onlyxuyang
    @verytoex
    确实对于像我这种用户量很小的应用够了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2720 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 12:23 · PVG 20:23 · LAX 04:23 · JFK 07:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.