在最近的一个项目中,我打算在页面上实现这样一个功能:
在网页上画出某种图形,上传到服务器后,返回一个src地址。这个地址可以用来分享到各种社交媒体。
这个功能看似非常简单,但要实现它还需要注意各种小的细节。
首先说下思路和技术要点:
按照这个思路马上就用jQuery.get()实现了一个测试用例。但结果非常让人失望!下面就是我遇到的各种问题:
1. post OR get首先是请求方式问题。
我们知道,http的get请求是利用url发送给服务器请求,受制于字节数限制,无法发送大的数据量。jquery中的get方法是其ajax方法的一个封装,基本原理仍是利用XMLHttpRequest对象发送了一个get请求,要发送图片这种较大的数据,用get方式显然力不从心,所以必须用post方式提交。
心想这么简单,于是把get改成post,over!
但结果失败了,因为这里遇到了另一个很严重的问题。
jQuery中的post方法在同域的情况下,可以顺利地以异步的方式提交form 数据,并用返回一个json结果。
所以,在同域的情况下,你可以这样
$.post("url",form1.serialize(),function(json){console.log(json)},'json');
服务器将处理后的结果用json形式返回给前端。
但在我的工作环境中,跨域是很常见的。而且我也希望能将这个功能组件化,让它在不同的子域中调用。
这样就必须要用到跨域的方式,于是很自然地想到jQuery中的jsonp。而且在jquery官方的ajax方法文档中,写明了dataType的值可以是"jsonp"。
于是又去改了一通,点了该死的按钮以后返回的是"HTTP 414 Request-URI too long"!God!
原来jQuery的post方法在跨域提交时会自动转换为GET方式,此时提交的数据已经超过了GET方式的请求字节限制!
这个时候,虽然有些气急败坏,但终于找到症结所在,就是下面要说的问题:
3. 如何在跨域的情况下异步发送post请求?这里要说明一个情况,就是为什么要用异步?
因为在我之前的许多项目中,大多都是在页面上发送一个异步请求,把服务器的结果实时地展示到页面上来,而不必刷新整个页面。当然会很自然地想到这样的实践方式。
我没有考虑到的是,之前的这些实践都是发送的get请求。当发送get请求时,如果是跨域,利用XMLHttpRequest对象发送get请求时可以用jsonp的方式跟服务器配合取回结果;如果是同域,那就更没有问题了。
但这次要上传图像数据,只能发送post请求,而且要异步实现。这可就犯难了。
好在守着互联网这个宝库,稍微搜索了一番以后,终于发现了一些有用的东西。
首先是这篇:POST跨域问题
看了这个后,知道了异步的情况下是不能跨域发送post请求的。想想也是,你在自己的网站上随便form发送了一大段数据到别人的网站,人家不搭理你就不错了,还要人家给你返回个“哥俩好”,可能么?
安全很重要,但我自己的两个子域虽然是跨域,但总归是一家子。一家人怎么才能不说两家话呢?
来看这个:跨域post请求实现方案小结
文档中列举出了目前实现跨域请求很多方法,如CORS、invisible iframe、server proxy、flash proxy。
还有一些文章列出了HTML5 WebSocket方法,但这种方式目前还不是标准,或者还没有流行起来,要想让功能以比较稳定的方式运行,最好还是用当下主流的方式。于是决定用iframe来实现。
4. iframe实现跨域提交的原理
iframe进行post“跨域”无刷新提交原理
看了这个图以后,基本上思路就明晰了。
我们用异步的目的是实现无刷新,既然post不能跨域异步提交,并且不异步也可以无刷新,那我们就不异步。我把form提交到本页面的一个空iframe中,这样不会造成页面的刷新。让服务器处理完后跳转到同域下的upload_result网页里,并给这个网页的url附加处理的结果。这样原始请求页的空iframe就加载了服务器跳转的那个页面的内容。同时,虽然这个upload_result页虽然跟我的原始页面不在一个子域,但我可以人为地设置它们的document.domain为同一个父域,当这个upload_result页面在我的原始请求页加载时,就可以执行父页面的callback函数。
5. 实现步骤明白了基本的原理,剩下的就是体力活了,下面看看怎么构建这样一个机制。
需要注意的是,传给服务器端的数据是base64编码的,需要先解码才能保存。
这样,当用户在canvas画完图,点击upload按钮时,把canvas的信息和回调函数名赋值给form,触发form提交。结果返回给空iframe,iframe 解析自身的返回结果,并触发父页面的callback函数。于是实现了页面的无刷新跨域post提交。Good!
参考:
延伸实现了canvas二进制图像的跨域上传和返回,那么在此基础上还可以做些更好玩的:比如图像编辑。
功能很简单:用户打开本地的图片文件并加载到canvas中,在画布上进行各种编辑,完了上传到服务器,并把返回的地址分享出去。
这里要用到的一个新东西就是HTML5的FileReader对象。利用它可以实现如本地预览等功能,我们用它来把选中的本地文件加载到canvas中,编辑后上传。