跨域post請求

跨域post請

今天被佈置了一個任務,要ajax跨域做一個post請求,叫我去查查要怎麼實現。

出於安全方面的考慮,ajax是不給跨域的(這裏指的ajax其實是瀏覽器的對象XMLHttpRequest()),除非要有服務器端代理,意思是同域下的服務器提供服務幫我們做一箇中轉。可以看到,我們請求的其實仍然是同域的服務器,事實上並沒有繞開跨域的問題,而是將它拋給服務器端解決,缺點也很明顯,必須服務器提供一個服務,而且數據多走了一個來回,效率就變得比較低了,好處是不會有兼容性的問題。我想要一個純前端的方案,所以server proxy排除。

這裏覺得要提一下,看網上很多關於ajax的文章,其中ajax指的並不是瀏覽器的那個對象,而是指一種不用刷新頁面就更新數據的手段,總會提jasonpm,iframe之類的,其實嚴格來說這些和ajax一點關係都沒有,這是一個概念上的不同吧。

那我們拋開ajax來看,其實需求就是要達到不刷新頁面的效果,不用ajax,跨域的解決方案其實挺多,jasonp算一個,但只能用get方法,post就無能爲力了。get的數據是存放在url中的,如果數據量太大就不能支持了,而且從安全方面考慮也差一點,排除。關於jasonp之前寫過一篇文章,有興趣的戳傳送門

flash也是可以post到任何域,原理是flash自己有一個跨域標準,在服務器要有一個文件定義允許跨域訪問的域名。flash如果讀到這個文件,並且允許當前域名訪問,就可以進行跨域了。但是flash的方式需要安裝插件,而且我不喜歡flash,所以也排除。

最後看來有兩種解決方法,一種是CORS,一種是iframe+form,前者高大上,後者相對比較麻煩,但兼容性較好,下面分別來說一下。

CORS

CROS是Cross-Origin Resource Sharing的縮寫,一看就知道是用來解決跨域問題的,其實這是通過在響應中加入允許跨域訪問的頭信息來實現的,標準比較新,所以並不是所有的瀏覽器都實現了。下面的圖是從caniuse.com截的圖。

caniuse_CORS

這樣看來兼容性也不算太差,自己做小項目可以玩玩,移動端的除了Opera,主流的也都可以。

詳細的標準可以上w3.org看看,英文苦手的話還是搜一下Cross-Origin Resource Sharing協議,看看博客一般也夠了。我自己也不喜歡看英文,但慢慢覺得想要研究什麼東西的話,還是得耐下性子看文檔。

響應頭有以下6個

1.Access-Control-Allow-Origin

取值可以是”origin-list-or-null | *”,但文檔中也說,實現的時候和標準有區別,並不支持list的形式,後面測試的時候來試試。

2.Access-Control-Allow-Credentials

credentials是憑證的意思,是預檢請求才會用到的一個屬性,放在後面一起講

3.Access-Control-Expose-Headers

這個頭規定哪些頭可以暴露給CROS API規範的API

4.Access-Control-Max-Age

規定那些預檢請求驗證通過的信息可以緩存多久

5.Access-Control-Allow-Methods

響應預檢請求,指定在通過預檢之後真正的跨域請求中,哪些method是可用的。

6.Access-Control-Allow-Headers

響應預檢請求,指定在通過預檢之後真正的跨域請求中,哪些頭是可用的。

請求頭有3個

1.Origin

該字段表明跨域請求的來源

2.Access-Control-Request-Method

預檢請求的字段,指明在真正的跨域請求中,要用哪種方法

3.Access-Control-Request-Headers

預檢請求的字段,指明在真正的跨域請求中,要用哪個字段

這裏看到預檢請求,真正的跨域請求等名詞,都是什麼意思呢。其實CORS跨域請求有兩種,一種是簡單的,一種是預檢的。

簡單的CORS跨域請求就是簡單的跨域GET或者POST,然後外域服務器端給你返回一個帶有Access-Control-Allow-Origin: “當前域” 頭信息的響應,就表明他允許你的跨域請求了。

預檢的CORS跨域請求就是說,要先發一個OPTION請求,告訴外域的服務器你要帶什麼頭信息,用什麼方法等等,問他肯不肯,這個請求就是所謂的預檢請求。等外域的服務器允許了你的預檢請求,後面纔會發起真正的跨域請求。

我們分別來對着兩種CORS跨域請求做下測試

一,簡單的CORS跨域請求

兩臺機子,各開一個web服務器,我這裏用的都是node。A機地址是192.168.1.104,B機器的地址是192.168.1.100.

A機器上服務的頁面代碼如下

<script type="text/javascript">
var cors_test = new XMLHttpRequest();
cors_test.open("GET", "http://192.168.1.100:8088");
cors_test.send();
<script>

可以預料,結果如下

ajax_cannot_load

ajax跨域請求被拒絕了,我們還看到,這裏提示了我們”No ‘Access-Control-Allow-Origin’ header is present on the requested resource.”那我們試試加上這個響應頭吧。Access-Control-Allow-Origin: *

allow_all

喲西,ajax跨域請求成功了,修改Access-Control-Allow-Origin字段的值爲”http://192.168.1.104:8088″

allow_ip

同樣成功了,我們來嘗試修改字段值爲一個list,看看是否如文檔所說是不可行的

allow_multiple_values

這裏告訴我們,多個值是不被允許的,請求不成功。

二,預檢的CORS跨域請求

我們加上一個請求頭’Content-Type’,指定內容類型爲’text/html’,意料中的報錯。

content_type

需要服務器用Access-Control-Allow-Headers來說明允許攜帶Content-Type的頭信息。我們在B機的服務器上添加允許攜帶該頭信息的字段後,再試一次。

option

可以看到,預檢請求的方法是OPTION,請求得到允許的響應之後才發了後面的GET請求,這纔是真正的CORS跨域請求。所以,預檢的方式就是先申請需要的選項,等待服務器應允之後再進行實際的數據交互,當業務有使用特定的方法,請求頭等需求是,就需要使用這種方式。

同源原則本來就是出於安全的考慮而制定的,我們在解決跨域訪問需求的統統是,也應該對安全性加以考慮,w3的文檔中對安全問題做了描述,不在本文討論範圍內,但實際項目實施的時候是很有必要考慮的

iframe+form

用iframe來解決問題,看上去好像很老土,但這是對瀏覽器的兼容性問題的妥協。我們都知道,除了帶src標籤可以跨域(jasonp的原理),form的提交也是可以跨域的,而iframe則是用來解決不用刷新的需求。

仍是兩臺機子,A機子上頁面的代碼如下


<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <script type="text/javascript">
        (function () {
            /*add iframe to document*/
            var iframe = document.createElement("iframe");
            document.body.appendChild(iframe);
            var iframe_name = "cross_domain_iframe";
            iframe.style.display = "none";
            iframe.contentWindow.name = iframe_name;

            /*add frome to document*/
            var form = document.createElement("form");
            form.target = iframe_name;
            form.action = "http://cp01-rdqa-dev149.cp01.baidu.com:8888/";
            form.method = "POST";

            /*add messages into input tag*/
            var input = document.createElement("input");
            input.type = "hidden";
            input.name = "test_message";
            input.value = "yoooooo";
            form.appendChild(input);

            document.body.appendChild(form);
            form.submit();

            iframe.onload = function () {
                console.log(iframe.contentWindow.document.body.innerHTML);
            }
        })();
        </script>
    </body>
</html>

B機器是一個測試機,我們來看看結果(公司機器,打個碼)
iframe_test1

可以看到post的請求成功了,服務器的響應也拿到了,但是響應內容log出來卻出錯了
iframe_test2

由於同源策略,iframe之間不能相互訪問,我們並不能直接得到這個POST的響應的內容。

做到這裏,只能大喊一聲QQ圖片20130628140742媽蛋,搞這麼多還是拿不到數據,有個屁用啊。

其實還是有辦法的,至少請求已經成功,而且數據也到了本地,我們可以通過瀏覽器窗口之間的postMessage來傳遞,仍然需要服務器端的配合,我們請求B站返回數據的時候,B站不能直接返回一個數據就完事兒了,而是要用postMessage把數據給丟出來

iframe_test3

A站頁面的js修改如下


iframe.onload = function () {
    window.frames[0].postMessage("testtest","http://xxxxxxxxxx:8888/");
}
window.addEventListener("message", function(event){
    console.log(event.data);
}, false);

然後終於,在控制檯上看到了輸出QQ圖片20130628134732

iframe_test4

我們來看看postMessage的支持情況

caniusepostMessage

情況比CORS好一些,但要兼容IE6和7之類的,估計就只能用flash了。

參考資料
http://www.w3.org/TR/cors/

http://www.cnblogs.com/shanyou/archive/2012/09/16/2687907.html

http://www.iteye.com/topic/600682
http://blog.sina.com.cn/s/blog_6940cab30101a1yj.html
http://stackoverflow.com/questions/298745/how-do-i-send-a-cross-domain-post-request-via-javascript#answer-6169703

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章