2011-07-16 61 views
46

這是可能的!參見下文。


首先,讓我用這個圖來講解如何異步文件上傳可以實現:是否可以執行異步跨域文件上傳?


對不起。我關閉了我的一個域,現在圖像已經消失。這是一個非常好的形象,但。這是在我發現Stack Overflow允許通過Imgur上傳圖像之前。


正如你所看到的,訣竅是讓HTTP響應加載到一個隱藏的IFRAME元素,而不是頁面本身。 (這是通過提交的JavaScript表單時設置表單元素的target屬性來完成的。)

這工作。但是,我面臨的問題是服務器端腳本位於不同的域。 FORM-submit是一個跨域HTTP請求。現在,服務器端腳本啓用了CORS,這使得我的網頁有權讀取從我的頁面到該腳本的HTTP請求的響應數據 - 但只有在通過Ajax接收HTTP響應時纔有效, ergo,JavaScript。

然而,INT這種情況下,響應指向IFRAME元素。一旦XML響應登陸到IFRAME,它的URL將成爲刪除腳本 - 例如http://remote-domain.com/script.pl

不幸的是,CORS不包括這種情況下(至少我認爲) - 我無法讀取IFRAME的內容,因爲它的URL不匹配的頁面(不同領域)的URL。我得到這個錯誤:

Unsafe JavaScript attempt to access frame with URL hxxp://remote-domain.com/script.pl from frame with URL hxxp://my-domain.com/outer.html. Domains, protocols and ports must match.

而且,由於IFRAME的內容是一個XML文檔,有IFRAME這可能使得使用postMessage或裏面的東西沒有JavaScript代碼。

所以我的問題是:我怎樣才能從IFRAME獲得XML內容是什麼?

正如我上面所說的,我能夠直接檢索跨域HTTP響應(啓用CORS),但似乎一旦它們加載到IFRAME中,我就無法讀取跨域HTTP響應。

而且好像這個問題是不可解決的不夠,讓我排除這些解決方案

  1. easyXDM並需要在遠程域終點類似的技術,

  2. 改變XML響應(包括腳本元素),

  3. 服務器端代理 - 我明白,我可以在我的域中的服務器端腳本可以引作爲代理。

因此,除了這兩個解決方案之外,這可以做到嗎?


它可以做到!

事實證明,可以僞造一個模仿multipart/form-data FORM提交的XHR請求(Ajax請求)(用於上圖將文件上傳到服務器)。

訣竅是使用FormData的構造函數 - 閱讀this Mozilla Hacks article瞭解更多信息。

這是你如何做到這一點:

// STEP 1 
// retrieve a reference to the file 
// <input type="file"> elements have a "files" property 
var file = input.files[0]; 

// STEP 2 
// create a FormData instance, and append the file to it 
var fd = new FormData(); 
fd.append('file', file); 

// STEP 3 
// send the FormData instance with the XHR object 
var xhr = new XMLHttpRequest(); 
xhr.open('POST', 'http://remote-domain.com/script.pl', true); 
xhr.onreadystatechange = responseHandler; 
xhr.send(fd); 

上述方法執行異步文件uplaod,這等同於由提交此表格上方的圖像中的描述和實現的常規文件上傳:

<form action="http://remote-domain.com/script.pl" 
     enctype="multipart/form-data" method="post"> 
    <input type="file" name="file"> 
</form> 

像老闆一樣:)

+0

如果您無法編輯遠程服務器響應,則不可以。如果您可以編輯上傳網站的來源,則可以使用hashchange或postmessage技巧。 – William

+0

如果您不太在意舊版瀏覽器,那麼您可以使用更新穎的上傳方法,在該方法中,您可以通過JS上傳文件並通過AJAX發佈文件。如果這聽起來像是一個好主意,請讓我知道,我會將其作爲答案發布。 –

+0

@Thomas我不關心舊版瀏覽器 - 事實上,即使它只在一個瀏覽器中工作,我也可以使用':)'。你能再詳細一點嗎?恐怕服務器腳本需要一個'

',我不確定我是否可以用JavaScript創建這樣的東西...... –

回答

9

只需用表單中的數據發送跨域XHR請求,而不是提交表單。 CORS僅適用於前者。

如果您必須以其他方式進行操作,請使用postMessage與框架協商。

And since the contents of the IFRAME is an XML document, there is no JavaScript code inside the IFRAME which could make use of postMessage or something.

這是如何阻止你?在XML中的任何位置在HTML或SVG名稱空間(<script xmlns="http://www.w3.org/1999/xhtml" type="application/ecmascript" src="..."/>)下包含腳本元素。

+1

[閱讀](http://jquery.malsup.com/form/#file-upload)。使用'XMLHttpRequest'對象上傳文件是不可能的。這就是爲什麼這個隱藏的IFRAME黑客首先被引入。至於在XML響應中包含SCRIPT元素,這是一個好主意。不幸的是,我的要求是,遠程域沒有任何修改(我沒有提到這一點)。因此,easyXDM不存在問題,並且也修改了XML響應。我的問題的目的是弄清楚這是否可以在不修改遠程域或使用代理的情況下完成。 –

+2

不,可以通過XHR上傳'Files'(只是'Blobs')。所有當前瀏覽器和IE10都支持使用W3C File API http://www.w3.org/TR/FileAPI/上傳文件 - 只需執行'xhr.send(file_input.files [0])'。 –

+0

我認爲我們必須等待5年才能放棄iframe解決方案作爲後備。 – BalusC

-1

如果可以,返回而不是XML的HTML頁面。
在此頁面,您可以在SCRIPT標籤使用命令:parent.postMessage

如果你要支持舊的瀏覽器(IE8 <爲主),你可以寫和下面的2Mb的短信讀取window.name

這兩種技術都允許您在不同域的幀之間傳遞字符串數據。

另一種技術是使用setInterval,它將使用JSONP反覆從父頁面調用遠程域以瞭解狀態。

在任何情況下,您都需要來自遠程域的合作才能獲取數據。

+0

不幸的不是。我無法以任何方式修改服務器端腳本。 –

-1

下面的辦法是工作在我的設置(火狐3.6):

<!-- hidden target frame --> 
<iframe name="load_target" id="load_target" onload="process(this);" src="#" ...> 

<!-- get data from iframe after load and process them --> 
<script type="text/javascript"> 
    function process(iframe) { 
     var data = iframe.contentWindow.document.body.innerHTML; 
     // got test data="<xml><a>b</a></xml>" 
    } 
</script> 

它工作在Chrome一樣好,但它是需要排除父頁面加載後第一個onload事件呼叫。這很容易通過設置在process()中測試的「全局」變量來完成。

ADDITION

的方法與被提交給一個URL形式

<form action="URL" method="post" enctype="multipart/form-data" target="load_target"> 

一起工作。這URL需要駐留在與父頁page.html相同的域。如果從REMOTE_URL數據被下載,然後URL將是對自己的域名一個PHP proxy.php與內容

<?php echo file_get_contents("REMOTE_URL"); ?> 

這是一個簡單的方法 - 然而,它可能是由條件排除(二)這個問題。我已經在這裏添加它來完成我的答案。

其他方法,僅考慮iframe,由MahemoffGeorges Auberger討論。

+0

你錯過了這一點。無法使用JavaScript讀取跨域IFRAME內容。只有當IFRAME中的頁面和包含該IFRAME的頁面具有相同的域時,您的代碼纔有效。加載'http:// google.com'到IFRAME並嘗試你的代碼 - 它不起作用。 –

+0

@Šime:感謝您的評論。我已經擴展了我的答案,只是爲了使其完成並適用於「遠程」頁面。我知道這個方法可能被你的問題排除在外。 – Jiri

+0

代理被問題明確禁止。 – BalusC

0

我認爲這不能用你描述的方式來完成。通常,如果您有跨域問題,您可以通過JSONp方法解決問題,但只適用於GET請求。使用HTML5,你可能會發送GET請求的二進制文件,但這僅僅是如此。

  • 一個解決方案是通過代理本地web服務器上的請求,使本地可用的遠程web服務可用。這會給你的本地網絡服務器帶來額外的負載,所以我可以想象它是不可行的。如果這些文件很少且很少,那麼這樣做會很好。

  • 另一種解決方案是在發送文件後開始輪詢服務器。您可以發送令牌並使用常規JSONp輪詢服務器的狀態。這樣你就不需要從iframe中讀取數據。

  • 將整個頁面放在運行在遠程服務器上的iframe中。這可能只是解決了這個問題,但是如果XML輸出是某個過程的最後一步,那麼這是非常可行的。

我敢肯定,你有一個很好的理由,處理服務器是在不同的領域,但如果不是,你就不會有這些問題。也許值得重新考慮?

+0

#1被明確排除在問題之外。 #2和#3只有在另一方的代碼庫處於完全控制之下時纔可能。根據OP對Mic的回答的評論,這顯然不是這種情況。 – BalusC