随着互联网业务的规模不断扩大,网站的开发方式也产生发巨大的变化。就拿上传来说,早些年通常都是网站自身包含上传模块,通过跳转方式或iframe方式进行上传。这没有涉及到跨域,所以对于上传结果的通知是很容易做到的。然而现在越来越多的做法是将上传功能分离开来,形成独立的上传域来提供上传服务。本文主要是针对跨域上传的上传结果通知分析并提供解决方案。
使用第三方存储,如七牛云存储
重定向上传方式
iframe上传方式
比如使用七牛云存储,这种方式第三方一般会提供各个语言的SDK,如果正巧您所使用的语言没有相应的SDK,那就只能自己去实现了。实现起来倒也不难,无非就是实现第三方授权的流程,然后通过文件上传协议提交文件到第三方。这种方式一般是通过服务器端来完成的,上传完成后第三方会有个上传反馈,然后返回到前端。
这是最传统的上传方式。各种上传方案都可以很容易的实现它。将上传表单重定向到上传网址,完成上传后可携带参数跳转回来。
这种方式是在页面中嵌入一个隐藏的iframe,上传表单提交到iframe来完成上传。现代浏览器都比较好的支持了iframe,所以这个方式也是通用的。这也是本文将讨论的上传方案。
如果是同域方式,因为iframe是嵌入在网页中,所以可以在iframe上传完成后通过parent来调用父框架提供的方法并将参数传入来实现通知,或者可以在上传完成后父框架获取iframe的引用并得到iframe的输出内容来实现通知。而如果是子域的方式,则还需要设置document.domain为子域。
因为是跨域方式,这将导致上传完成后目标iframe没有权限去调用父框架中的方法,并且父框架也没有权限去访问子框架的内容,所以无法进行通知上传结果。
是的,一般来说,在iframe上传完成后都会输出数据或一段js代码。但是如果输出的是一个iframe呢?本文将用2个host来演示这种情况。www.aw.com 是需要上传的网站,www.file.com 是提供上传的网站。我们来看个图:
上图很清晰的体现了这种包含关系。A上传到B,B上传完成后输出iframe,并指向A域名的一个页面,也就是上图中的C。A和C是同域的,即使是A不能访问C,但是C却可以通过parent.parent来访问A。为了演示,我配置了以下host:
aw目录结构如下:
www/aw
----app.py
----templates
--------index.html
--------cross.html
app.py源码:
#coding: utf8 import os from datetime import timedelta from flask import Flask, render_template import urllib app = Flask(__name__) app.secret_key = os.urandom(24) app.permanent_session_lifetime = timedelta(seconds=24 * 60 * 60) @app.route('/') def index(): return render_template('index.html', **dict(upload_url='http://www.file.com:6667/upload')) @app.route('/cross.html') def cross(): return render_template('cross.html') if __name__ == '__main__': app.run( host="0.0.0.0", port=int("6666"), debug=True )
index.html源码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>aw</title> <meta name="author" content="" /> <meta http-equiv="X-UA-Compatible" content="IE=7" /> <meta name="keywords" content="aw" /> <meta name="description" content="aw" /> <script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.1.0/jquery.min.js"></script> </head> <body> <form enctype="multipart/form-data" action="{{ upload_url }}" method="POST" target="upload_iframe"> <input name="UploadName" type="file" /> <input type="submit" value="上传" /> </form> <iframe id="upload_iframe" name="upload_iframe" width="0" height="0" style="display: none;"></iframe> <script type="text/javascript"> // 上传完成回调 function uploadComplete(data) { console.log('上传完成:' + data); } </script> </body> </html>
cross.html源码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>aw cross</title> <meta name="author" content="" /> <meta http-equiv="X-UA-Compatible" content="IE=7" /> <meta name="keywords" content="aw cross" /> <meta name="description" content="aw cross" /> <script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.1.0/jquery.min.js"></script> </head> <body> <script type="text/javascript"> $(function() { var href = location.href; var index = href.indexOf('?'); if (index < 0) { var data = 'result=upload_error'; } else { var data = href.substring(index + 1); } // 将数据通过调用parent.parent.uploadComplete方法来传递 parent.parent.uploadComplete(data); }); </script> </body> </html>
file目录结构如下:
www/file
----app.py
app.py源码:
#coding: utf8 import os from datetime import timedelta from flask import Flask, request, render_template import urllib app = Flask(__name__) app.secret_key = os.urandom(24) app.permanent_session_lifetime = timedelta(seconds=24 * 60 * 60) @app.route('/upload', methods=['POST']) def index(): '''忽略上传文件处理细节,直接返回数据''' cross_html = 'http://www.aw.com:6666/cross.html?result=upload_complete' return '<iframe width="0" height="0" style="display:none;" src="%s"></iframe>' % cross_html if __name__ == '__main__': app.run( host="0.0.0.0", port=int("6667"), debug=True )
这样就实现了跨域回调,知道了原理其实就不难了,如有需要可进行封装,如实现跨域检测,如果是同域则使用同域的方式进行回调,如果是跨域,则使用跨域的方式进行回调。并且可实现多文件上传方案。
上传超时是必须处理的上传错误之一。对于iframe上传(这里并没有特指同域或跨域)来说,超时机制将会限制整个上传所占用的时间,这是有必要的,否则如果网络异常,前端将一直显示正在上传中,这是极不友好的用户体验。另一方面也是为了安全考虑。
其实实现iframe的上传超时机制非常简单。通过setTimeout在固定时间后去remove iframe,当然如果上传是成功的则会提前clearTimeout。所以如果setTimeout成功执行则肯定是超时了,否则就是上传完成(至于是上传成功或上传失败则由服务器端返回的状态码来确定)。比如:
index.html源码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>aw</title> <meta name="author" content="" /> <meta http-equiv="X-UA-Compatible" content="IE=7" /> <meta name="keywords" content="aw" /> <meta name="description" content="aw" /> <script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.1.0/jquery.min.js"></script> </head> <body> <form enctype="multipart/form-data" action="{{ upload_url }}" method="POST" target="upload_iframe"> <input name="UploadName" type="file" /> <input type="submit" value="上传" /> </form> <iframe id="upload_iframe" name="upload_iframe" width="0" height="0" style="display: none;"></iframe> <script type="text/javascript"> // 上传完成回调 function uploadComplete(data) { timeout_id && clearTimeout(timeout_id); console.log('上传完成:' + data); } var timeout_id = setTimeout(function() { $("#upload_iframe").remove(); uploadComplete("上传超时") }, 10000); </script> </body> </html>
大致上就是这样,这是很常用的跨域上传方案,有必要认真理解并加以实践。