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

2015-10-28 22:54:39 +08:00
 banxi1988

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
4814 次点击
所在节点    Python
6 条回复
onlyxuyang
2015-10-28 23:37:33 +08:00
七牛太贵了 不是商业应用根本搞不起
bdbai
2015-10-28 23:44:33 +08:00
@onlyxuyang 算上免费额度的话,小微级应用还是可以接受的,毕竟他们服务很棒。
5thcat
2015-10-29 00:36:10 +08:00
FormData + ajax 不行吗?要兼容老浏览器?
typcn
2015-10-29 01:34:03 +08:00
什么年代了。。。。。。。。你的用户用的是 IE6 么。。。
verytoex
2015-10-29 09:56:00 +08:00
@onlyxuyang 免费版好像有 10G 流量,小站基本够用
banxi1988
2015-10-29 10:31:08 +08:00
@5thcat
FormData + ajax IE 10 才有的。

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


@bdbai
@onlyxuyang
@verytoex
确实对于像我这种用户量很小的应用够了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/231853

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX