劉昆 李富濤 舒亞非
摘要:該文列舉了當前幾種常見的文件上傳方式,該文基于云服務(wù)研究了文件跨域上傳的解決方案,并指出了每一種文件上傳技術(shù),在當前的技術(shù)背景下各自存在的優(yōu)缺點。通過對每一種技術(shù)優(yōu)缺點的權(quán)衡,找出一種合適的方案來解決項目中文件跨域上傳的問題。
關(guān)鍵詞:上傳;跨域;同源策略
中圖分類號:TP311 文獻標識碼:A 文章編號:1009-3044(2016)18-0071-02
1 背景
筆者最近在開發(fā)一個基于 HTML5 的社區(qū)活動應(yīng)用商店的項目,該項目主要是面向活動組織者并以 Web API 的方式提供一系列的在線服務(wù),以方便活動發(fā)起者進行活動的籌備和組織。其中,有一項云存儲功能,方便第三方活動發(fā)起者在他自己的網(wǎng)站或 APP中通過調(diào)用上傳API,將活動相關(guān)文件譬如活動照片、視頻等存儲到活動云服務(wù)器。該功能涉及文件異步上傳、跨域上傳等技術(shù)難點,即本文的研究重點。
2 研究
2.1 傳統(tǒng)上傳方式
瀏覽器一般都是通過 HTTP 的 POST 方法來實現(xiàn)基本的上傳操作。最常見的數(shù)據(jù)提交方式就是瀏覽器的原生表單,使用其默認的enctype屬性設(shè)置application/x-www-form-urlencoded。但是這僅限于上傳基本的文本數(shù)據(jù),如果要上傳文件,可以設(shè)置enctype屬性為 multipart/form-data。具體代碼如下:
但是使用傳統(tǒng)方式上傳存在許多的弊端。首先,該上傳方式屬于“同步上傳”,就是在上傳過程中會把頁面“鎖死”,用戶只能夠等待文件上傳結(jié)束頁面刷新完,才能進行下一步的操作。不僅如此,使用傳統(tǒng)上傳文件方式,上傳文件的當前進度以及上傳的速度都不得而知,沒有一個很好的顯示交互過程,就用戶角度來說體驗很差。而且對于有大量上傳任務(wù)的用戶來說,這種“同步上傳”的方式是不能對其進行批量處理的。
2.2 異步上傳方式
由于“同步上傳”存在的弊端,尋找更優(yōu)的方式來解決上傳時頁面“鎖死”和交互信息不足的問題,成為了開發(fā)人員當前急需解決的事。
顯然同步上傳已經(jīng)無法解決上述問題,自然就提出異步的解決方式。異步上傳文件的方式有很多,本文主要介紹一下異步上傳文件中使用 Flash、Ajax 以及基于 HTML5 的文件上傳方式。
2.2.1 Flash上傳
Flash 是比較常見的上傳方法之一。在 Flash 中上傳文件只要對 FileReference 進行簡單的封裝就可以實現(xiàn)。通過 FileReference類的方法可使應(yīng)用程序本地加載和保存數(shù)據(jù)文件,并與遠程服務(wù)器之間傳輸文件數(shù)據(jù)。另外,F(xiàn)lash 的優(yōu)勢在于有較好的文件上傳隊列,能夠自動逐個上傳文件,將上傳的進度很好的反饋。但是第三方插件畢竟還是需要瀏覽器的支持,比如 Safari 不支持 Flash,也就造成 Flash 會存在兼容性的問題,并且就目前 Flash 的發(fā)展來看,F(xiàn)lash 也因為資源的消耗嚴重而不斷被放棄使用。也因為如此,其他的上傳技術(shù)將得到進一步發(fā)展。
2.2.2 iframe上傳
因為 Flash 這些第三方插件存在兼容性的問題,我們需要一種能夠在瀏覽器上直接使用 Web 技術(shù)來解決的方法。因此想到使用 iframe 來模擬文件的異步上傳操作。實現(xiàn)這種方法很簡單,只需要設(shè)置 form 表單的 target 屬性到 iframe 的 name ??傮w來說,這種基于 iframe 的表單上傳,因為異步、簡單,適用于一些簡單的上傳應(yīng)用場景。代碼如下:
2.2.3 HTML5 新接口
隨著 HTML5 的發(fā)布,添加了一些文件讀取的新接口,瀏覽器處理用戶電腦里的文件可以使用以下三個方法:FormData Interface、FileList Interface、FileReader API。這三個方法分別實現(xiàn)了瀏覽器從文件打開、選擇、讀取三個功能。而且由于XMLHttpRequest Level 2很好地兼容了這三個方法,從而允許我們可以使用 FormData 將表單中的對象狀態(tài)轉(zhuǎn)換成可保持或傳輸?shù)母袷剑瑥亩鴮崿F(xiàn)異步上傳一個二進制文件。
document.getElementById("myform").onsubmit = function(e) {
e.preventDefault();
var f = e.target,
formData = new FormData(f),
xhr = new XMLHttpRequest();
xhr.open("POST", f.action);
xhr.send(formData);
}
完成將 FormData 對象通過send()方法傳遞后,對應(yīng)的 HTTP 頭信息會自動幫你設(shè)置好。不需要再糾結(jié)編碼的問題。而且就兼容性來說,目前主流的最新版本的瀏覽器都已經(jīng)兼容了 HTML5,IE 也在 10 以后的版本也開始兼容 HTML5。
雖然 HTML5 已經(jīng)得到主流最新版本的瀏覽器的支持,但還是有很多舊版本的瀏覽器是無法使用 HTML5 的新特性。如果還是要考慮到這方面用戶的上傳需求,那么就必須結(jié)合 Flash 或者 iframe 的上傳方式。這樣子就可以全方位地兼容市面上的瀏覽器了。
3實踐
針對于以上的研究,筆者了解到目前很多公司的解決方法都是通過服務(wù)器代理上傳,這種方法很消耗服務(wù)器的資源,但是好處在于它不受到限制,技術(shù)上實現(xiàn)起來比較容易。為了提高用戶的體驗,本文試圖通過客戶端跨域直傳的方式,將本地的文件直接上傳的云存儲平臺上,這樣可以使用云平臺提供的 CDN 加速,大大提高了用戶上傳文件的速度,然而對于服務(wù)器代理上傳,客戶端直傳在技術(shù)上實現(xiàn)起來比較復雜,主要是因為瀏覽器做了很多的限制。
首先,先來了解一下兩種方式的上傳流程。對于服務(wù)器代理上傳,很多開發(fā)者采用的做法是先上傳到服務(wù)器,然后由服務(wù)器把文件上傳到云存儲平臺。流程如圖所示:
客戶端(瀏覽器)=>服務(wù)器 =>云存儲服務(wù),這種上傳方式存在文件上傳速度受限、擴展性不佳、費用高等問題。針對以上問題,本地瀏覽器直傳技術(shù)將文件直接上傳到云存儲服務(wù)上,將會為開發(fā)者節(jié)省大量的維護成本并提高上傳的速度。流程如圖所示:
由于瀏覽器在安全方面的考慮,實施了同源策略來限制瀏覽器的跨域訪問。因此實現(xiàn)直傳首先需要解決客戶端瀏覽器的跨域問題,在 iframe 中可以選擇使用片段標識符技術(shù)監(jiān)聽新消息。該技術(shù)通過每隔 N 毫秒輪詢 URL,來獲取到當前需要上傳的信息。但是使用這種 URL 拼接的方式,對于大文件的上傳會造成限制,主要原因是瀏覽器對 URL 的限制。
如果頁面在主域名相同而子域名不同的情況下,可以設(shè)置 document.domain 讓它們同域。而在當前的業(yè)務(wù)場景之下,項目本身服務(wù)器提供的域名和云服務(wù)提供商所提供的域名,它們之間的主域名是不同的,本身就不滿足使用 document.domain 的前提條件,所以通過設(shè)置 document.domain 來實現(xiàn)跨域不是很好的選擇。
當然在 HTML5 規(guī)范中引入了 window.postMessage 新特性,可以用于安全跨域通信。window.postMessage 基于事件設(shè)計的機制解決了跨域的問題。不再是直接訪問一個文檔的屬性和方法,而是向文檔發(fā)送消息而后等待響應(yīng)。這種方式要求雙方建立一種雙向的通信通道,從而消除對文檔的惡意訪問攻擊。最后我們考慮使用 window.postMessage 來實現(xiàn)跨域的通信。
解決完跨域問題,緊接著需要云存儲服務(wù)商提供有效的的簽名授權(quán),初始化服務(wù)器的配置之后,我們的服務(wù)器所要做的就是向客戶端提供上傳的憑證(token),通過憑證來達到客戶端直傳文件到云存儲上。通過 DNS 的智能解析,云存儲可以連接到最近的 ISP 服務(wù)商節(jié)點,對于原本的上傳流程來說,這樣的方式速度上獲得了很大的提升,也減輕了服務(wù)器的壓力。
4 結(jié)束語
其實全文所提到的跨域問題,都是瀏覽器開發(fā)商出于安全性的考慮實行“同源策略”,對瀏覽器進行了限制。但隨著HTML5 的普及,主流的瀏覽器更新速度加快,越來越多的接口會被添加進來,可能困擾許多人的跨域問題在未來會很容易的得到解決。當然,就目前來看,其實很多的插件也都已經(jīng)很好地實現(xiàn)跨域的問題。而且很多云服務(wù)平臺的提供商,如七牛云等,都有提供相應(yīng)的 SDK,開發(fā)人員只需要依照相應(yīng)的文檔就可以很方便地實現(xiàn)跨域直傳技術(shù)。而且使用第三方的云存儲平臺,可以大大提高用戶上傳和下載的速度。
參考文獻:
[1] Ben Vinegar,AntonKovalyov.第三方JavaScript編程[M]. 郭凱,譯.北京:人民郵電出版社,2015.
[2] Mozilla Developer Network[EB/OL].https://developer.mozilla.org/zh-CN/.
[3] 阮一峰. 文件上傳的漸進式增強[EB/OL]. (2012-08-10)[2016-05-10]. http://www.ruanyifeng.com/blog/2012/08/file_upload.html.
[4] Dimitar Ivanov. AJAX File Upload[EB/OL]. (2015-10-03)[2016-05-10]. http://zinoui.com/blog/ajax-file-upload.
[5] 廖學芝. HTML5文件上傳組件的深度剖析[EB/OL]. (2014-03-29)[2016-05-10]. https://segmentfault.com/a/1190000000491128.