跨域的幾種方式

什麼是跨域?

跨域:就是不同域之間進行相互資源請求;

例如:http://www.test.com/index.com  請求  http://www.test01.com/server.php

即進行不同的域名下的資源調用。

注意:域名對應的IP地址也算是跨域操作;例:127.0.0.1和localhost雖然對應,但在地址欄中算兩個域。

爲什麼出現跨域? 

瀏覽器在解析JavaScript出於安全方面的考慮,只允許在同域名下頁面進行相互資源請求調用,不允許調用其他域名下的頁面的對象;簡單的理解就是因爲JavaScript同源策略的限制

注意:跨域並不是請求發不出去,請求能發出去,服務器能收到請求並正常返回結果,只是結果被瀏覽器攔截了,所以頁面無法正常使用數據。

什麼是同源策略?

同源策略要求源相同才能進行正常的資源交互,即要求當前頁面與調用資源的協議,域名、子域名、端口完全一致;不一致則就是跨域。

一個域名地址的組成:

參考如下圖理解:

說明:

1、如果是協議和端口造成的跨域問題「前臺」是無能爲力的。

2、在跨域問題上,域僅僅是通過「URL的首部」來識別而不會根據域名對應的IP地址是否相同來判斷。「URL的首部」可以理解爲「協議, 域名和端口必須匹配」。

 同源策略的限制

同源策略限制一個資源地址加載的文檔或腳本與來自另一個資源地址的資源進行交互。這是瀏覽器的一個用於隔離潛在惡意文件的關鍵的安全機制。它的存在可以保護用戶隱私信息,防止身份僞造等(讀取Cookie)。

同源策略限制內容有:

1、Cookie、LocalStorage、IndexedDB等存儲性內容

2、不允許進行DOM節點的操作

3、不能進行AJAX請求

同源策略的天然支持跨域請求的特性屬性

1、<img  src="xxx" />

2、<link  href="xxx" />

3、<script  src="xxx" ></script>

解決同源策略的方法

方法一、處理跨域的jsonp方法

JSONP原理

利用<script  src="xxx">元素的這個天然支持跨域的策略,網頁可以得到從其他來源動態產生的 JSON 數據。JSONP請求一定需要對方的服務器做支持纔可以。

JSONP和AJAX對比

JSONP和Ajax相同,都是客戶端向服務器端發送請求,從服務器端獲取數據的方式。但Ajax屬於同源策略,JSONP屬於非同源策略(跨域請求)。

JSONP優缺點

JSONP優點是兼容性好,可用於解決主流瀏覽器的跨域數據訪問的問題。缺點是僅支持get方法具有侷限性。

 

JSONP的流程(以第三方API地址爲例,不必考慮後臺程序)

  • 聲明一個回調函數,其函數名(如fn)當做參數值,要傳遞給跨域請求數據的服務器,函數形參爲要獲取目標數據(服務器返回的data)。

  • 創建一個 <script>標籤,把那個跨域的API數據接口地址,賦值給script的src,還要在這個地址中向服務器傳遞該函數名(可以通過問號傳參 :?callback=fn)。

    • 服務器接收到請求後,需要進行特殊的處理:把傳遞進來的函數名和它需要給你的數據拼接成一個字符串,例如:傳遞進去的函數名是fn,它準備好的數據是 fn([{"name":"jianshu"}])。

  • 最後服務器把準備的數據通過HTTP協議返回給客戶端,客戶端再調用執行之前聲明的回調函數(fn),對返回的數據進行操作。

<script type="text/javascript">
    function fn(data){
        alert(data.msg);
    }
</script>
<script type="text/javascript" src="http://crossdomain.com/jsonServerResponse?jsonp=fn">

其中 fn 是客戶端註冊的回調的函數,目的獲取跨域服務器上的json數據後,對數據進行在處理。

最後服務器返回給客戶端數據的格式爲:

fn({ msg:'this  is  json  data'})

jQuery的jsonp形式

JSONP都是GET和異步請求的,不存在其他的請求方式和同步請求,且jQuery默認就會給JSONP的請求清除緩存。

$.ajax({
    url:"http://crossdomain.com/jsonServerResponse",
    dataType:"jsonp",
    type:"get",//可以省略
    jsonpCallback:"fn",//->自定義傳遞給服務器的函數名,而不是使用jQuery自動生成的,可省略
    jsonp:"jsonp",//->把傳遞函數名的那個形參callback變爲jsonp,可省略
    success:function(data){
    console.log(data);}
});

方法二:處理跨域方法二——CORS

CORS原理

整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。因此,實現CORS通信的關鍵是服務器。只要服務器實現了CORS接口,就可以跨源通信

CORS優缺點

CORS要求瀏覽器(>IE10)和服務器的同時支持,是跨域的根本解決方法,由瀏覽器自動完成。

優點在於功能更加強大支持各種HTTP Method,缺點是兼容性不如JSONP。

只需要在服務器端做一些小小的改造即可:

header("Access-Control-Allow-Origin:*");
header("Access-Control-Allow-Methods:POST,GET");

例如:網站 http://localhost:63342/ 頁面要請求 http://localhost:3000/users/userlist 頁面,userlist頁面返回json字符串格 {name:'Mr.Cao',gender:'male',career:'IT Education'}

//在服務器端設置同源策略地址
router.get("/userlist",function(req, res,next){
    var user ={name:'Mr.Cao', gender:'male', career:'IT Education'};
    res.writeHeader(200,{"Access-Control-Allow-Origin":'http://localhost:63342'});
    res.write(JSON.stringify(user));
    res.end();
});

在響應頭上添加 Access-Control-Allow-Origin 屬性,指定同源策略的地址。同源策略默認地址是網頁的本身。只要瀏覽器檢測到響應頭帶上了CORS,並且允許的源包括了本網站,那麼就不會攔截請求響應

header("Access-Control-Allow-Origin:*");  
//    *  :表示所有其他的域可以向當前域發送請求

//或者

header("Access-Control-Allow-Origin:http://test.com:80(相同默認不寫)");    
//  http://test.com:80 表示指定具體的這個域可以向當前域發送請求。
//當然也可以指定多個特定域名,多個地址的話用逗號分隔即可。

方法三:WebSocket

Websocket是HTML5的一個持久化的協議,它實現了瀏覽器與服務器的全雙工通信,同時也是跨域的一種解決方案。WebSocket和HTTP都是應用層協議,都基於 TCP 協議。但是 WebSocket 是一種雙向通信協議,在建立連接之後,WebSocket 的 server 與 client 都能主動向對方發送或接收數據。同時,WebSocket 在建立連接時需要藉助 HTTP 協議,連接建立好了之後 client 與 server 之間的雙向通信就與 HTTP 無關了。

原生WebSocket API使用起來不太方便,我們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。

//--------------------------------------前端代碼:------------------------------
<div>user input:<input type="text">
</div><script src="./socket.io.js"></script>
<script>
var socket =io('http://www.domain2.com:8080');
// 連接成功處理
socket.on('connect', function(){   
    // 監聽服務端消息
    socket.on('message',function(msg){
        console.log('data from server: ---> '+ msg);
    });
 
    // 監聽服務端關閉
    socket.on('disconnect', function() { 
        console.log('Server socket has closed.');     
    });
});
    document.getElementsByTagName('input')[0].onblur = function() {
        socket.send(this.value);
    };
</script>




//---------------------------------------Nodejs socket後臺:---------------------------
var http = require('http');
var socket =require('socket.io');
// 啓http服務
var server = http.createServer(function(req, res) {
    res.writeHead(200, {'Content-type': 'text/html'});
    res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
// 監聽socket連接
socket.listen(server).on('connection', function(client) {
    
// 接收信息
    client.on('message', function(msg) {        
        client.send('hello:' + msg);
        console.log('data from client: ---> ' + msg);    
    });
    
// 斷開處理
    client.on('disconnect', function() {
        console.log('Client socket has closed.');     
    });
});

方法四:postMessage

如果兩個網頁不同源,就無法拿到對方的DOM。典型的例子是iframe窗口和 window.open方法打開的窗口,它們與父窗口無法通信。HTML5爲了解決這個問題,引入了一個全新的API:跨文檔通信 API(Cross-document messaging)。這個API爲window對象新增了一個 window.postMessage 方法,允許跨窗口通信,不論這兩個窗口是否同源。

postMessage方法的第一個參數是具體的信息內容,第二個參數是接收消息的窗口的源(origin),即"協議 + 域名 + 端口"。也可以設爲 *,表示不限制域名,向所有窗口發送。

接下來我們看個例子: http://localhost:63342/index.html頁面向 http://localhost:3000/message.html傳遞「跨域請求信息」

//發送信息頁面 http://localhost:63342/index.html
<html lang="en">  
<head>    
<meta charset="UTF-8">      
<title>跨域請求</title>   
</head>  
<body>      
<iframe src="http://localhost:3000/users/reg" id="frm"></iframe>      
<input type="button" value="OK" onclick="run()">  
</body>  
</html>  
<script>     
function  run(){          
var frm=document.getElementById("frm");  
        frm.contentWindow.postMessage("跨域請求信息","http://localhost:3000");     
}  
</script>
//接收信息頁面 http://localhost:3000/message.html
 window.addEventListener("message",function(e){  
//通過監聽message事件,可以監聽對方發送的消息。
  console.log(e.data);  
},false);

所有的跨域都必須經過信息提供方的允許。如果未經允許即可獲取,那是瀏覽器同源策略出現漏洞。