整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          小猿圈Html5自學之WebSocket跨域問題解決

          小猿圈Html5自學之WebSocket跨域問題解決

          近有些網友說自己遇到問題不知道怎么解決,特別是有些自學web前端的同學,今天小猿圈web前端講師就給大家簡單的講講WebSocket跨域問題解決辦法,希望對你有所幫助。

          WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信,同時允許跨域通訊,是server push技術的一種很好的實現。我們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。

          項目中遇到javascript跨域問題,父頁面和子頁面要通信,并且父子頁面跨域,怎么辦?

          項目中要保證父子頁面通信是點對點,需要在服務端建立對父子頁面WebSocket的對應關系,即父頁面發的消息只被子頁面收到,子頁面的消息也只被父頁面收到我們做了以下工作,嚴格保證了

          WebSocket通信是點對點:

          一是建立WebSocket鏈接的URL加上時間戳保證通信會話是唯一的;

          二是在服務端保證父子頁面WebSocket一一對應關系。父子頁面的WebSocket在Open時都會向服務端發出消息進行注冊,建立Senssion之間的對應關系。然后父子頁面就可通過雙方約束的通信協議進行通信了。

          這里我們寫個demo:

          var p=document.getElementsByTagName(‘p’)[0];

          var io=io.connect(‘http://127.0.0.1:3001’);

          io.on(‘data’,function(data){

          alert(‘2S后改變數據’);

          p.innerHTML=data

          });

          服務器端

          var io=require(‘socket.io’)(server);

          io.on(‘connection’,function(client){

          client.emit(‘data’,’hello WebSocket from 3001.’);

          });

          今天就說到這里,希望對大家有所幫助,同時大家如果不想太花時間去做WebSocket這塊,可以嘗試使用三方WebSocket,類似GoEasy 極光之類的。

          上述就是小猿圈web前端講師為大家講解前WebSocket跨域問題解決,希望你能在自學前端中找到自己學習方法以及真正有用的學習網站,如果你沒有可以到小猿圈這里,這里不僅有專業的學習視頻還有老師指導你學習幫助你進步,希望你在不斷進步中加快成長。

          么是跨域

          跨域,是指瀏覽器不能執行其它網站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對JavaScript實施的安全限制。

          同源策略限制了一下行為:

          • Cookie、LocalStorage 和 IndexDB 無法讀取
          • DOM 和 JS 對象無法獲取
          • Ajax請求發送不出去

          常見的跨域場景

          所謂的同源是指,域名、協議、端口均為相同。

          http://www.nealyang.cn/index.html 調用   http://www.nealyang.cn/server.php  非跨域
          
          http://www.nealyang.cn/index.html 調用   http://www.neal.cn/server.php  跨域,主域不同
          
          http://abc.nealyang.cn/index.html 調用   http://def.neal.cn/server.php  跨域,子域名不同
          
          http://www.nealyang.cn:8080/index.html 調用   http://www.nealyang.cn/server.php  跨域,端口不同
          
          https://www.nealyang.cn/index.html 調用   http://www.nealyang.cn/server.php  跨域,協議不同
          
          localhost   調用 127.0.0.1 跨域
          

          跨域的解決辦法

          jsonp跨域

          jsonp跨域其實也是JavaScript設計模式中的一種代理模式。在html頁面中通過相應的標簽從不同域名下加載靜態資源文件是被瀏覽器允許的,所以我們可以通過這個“犯罪漏洞”來進行跨域。一般,我們可以動態的創建script標簽,再去請求一個帶參網址來實現跨域通信

          //原生的實現方式
          let script = document.createElement('script');
          
          script.src = 'http://www.nealyang.cn/login?username=Nealyang&callback=callback';
          
          document.body.appendChild(script);
          
          function callback(res) {
            console.log(res);
          }
          

          當然,jquery也支持jsonp的實現方式

          $.ajax({
              url:'http://www.nealyang.cn/login',
              type:'GET',
              dataType:'jsonp',//請求方式為jsonp
              jsonpCallback:'callback',
              data:{
                  "username":"Nealyang"
              }
          })
          

          雖然這種方式非常好用,但是一個最大的缺陷是,只能夠實現get請求

          document.domain + iframe 跨域

          這種跨域的方式最主要的是要求主域名相同。什么是主域名相同呢?
          www.nealyang.cn aaa.nealyang.cn ba.ad.nealyang.cn 這三個主域名都是nealyang.cn,而主域名不同的就不能用此方法。

          假設目前a.nealyang.cn 和 b.nealyang.cn 分別對應指向不同ip的服務器。

          a.nealyang.cn 下有一個test.html文件

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>html</title>
              <script type="text/javascript" src = "jquery-1.12.1.js"></script>
          </head>
          <body>
              <div>A頁面</div>
              <iframe 
              style = "display : none" 
              name = "iframe1" 
              id = "iframe" 
              src="http://b.nealyang.cn/1.html" frameborder="0"></iframe>
              <script type="text/javascript">
                  $(function(){
                      try{
                          document.domain = "nealyang.cn"
                      }catch(e){}
                      $("#iframe").load(function(){
                          var jq = document.getElementById('iframe').contentWindow.$
                          jq.get("http://nealyang.cn/test.json",function(data){
                              console.log(data);
                          });
                      })
                  })
              </script>
          </body>
          </html>
          

          利用 iframe 加載 其他域下的文件(nealyang.cn/1.html), 同時 document.domain 設置成 nealyang.cn ,當 iframe 加載完畢后就可以獲取 nealyang.cn 域下的全局對象,
          此時嘗試著去請求 nealyang.cn 域名下的 test.json (此時可以請求接口),就會發現數據請求失敗了~~ 驚不驚喜,意不意外!!!!!!!

          數據請求失敗,目的沒有達到,自然是還少一步:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>html</title>
              <script type="text/javascript" src = "jquery-1.12.1.js"></script>
              <script type="text/javascript">
                  $(function(){
                      try{
                          document.domain = "nealyang.com"
                      }catch(e){}
                  })
              </script>
          </head>
          <body>
              <div id = "div1">B頁面</div>
          </body>
          </html>
          

          此時在進行刷新瀏覽器,就會發現數據這次真的是成功了~

          window.name + iframe 跨域

          window.name屬性可設置或者返回存放窗口名稱的一個字符串。他的神奇之處在于name值在不同頁面或者不同域下加載后依舊存在,沒有修改就不會發生變化,并且可以存儲非常長的name(2MB)

          假設index頁面請求遠端服務器上的數據,我們在該頁面下創建iframe標簽,該iframe的src指向服務器文件的地址(iframe標簽src可以跨域),服務器文件里設置好window.name的值,然后再在index.html里面讀取改iframe中的window.name的值。完美~

          <body>
            <script type="text/javascript"> 
              iframe = document.createElement('iframe'),
              iframe.src = 'http://localhost:8080/data.php';
              document.body.appendChild(iframe);
              iframe.onload = function() {
                console.log(iframe.contentWindow.name)
              };
            </script>
          </body>
          

          當然,這樣還是不夠的。

          因為規定如果index.html頁面和和該頁面里的iframe框架的src如果不同源,則也無法操作框架里的任何東西,所以就取不到iframe框架的name值了,告訴你我們不是一家的,你也休想得到我這里的數據。
          既然要同源,那就換個src去指,前面說了無論怎樣加載window.name值都不會變化,于是我們在index.html相同目錄下,新建了個proxy.html的空頁面,修改代碼如下:

          <body>
            <script type="text/javascript"> 
              iframe = document.createElement('iframe'),
              iframe.src = 'http://localhost:8080/data.php';
              document.body.appendChild(iframe);
              iframe.onload = function() {
                iframe.src = 'http://localhost:81/cross-domain/proxy.html';
                console.log(iframe.contentWindow.name)
              };
            </script>
          </body>
          

          理想似乎很美好,在iframe載入過程中,迅速重置iframe.src的指向,使之與index.html同源,那么index頁面就能去獲取它的name值了!但是現實是殘酷的,iframe在現實中的表現是一直不停地刷新,
          也很好理解,每次觸發onload事件后,重置src,相當于重新載入頁面,又觸發onload事件,于是就不停地刷新了(但是需要的數據還是能輸出的)。修改后代碼如下:

          <body>
            <script type="text/javascript"> 
              iframe = document.createElement('iframe');
              iframe.style.display = 'none';
              var state = 0;
          
              iframe.onload = function() {
                if(state === 1) {
                    var data = JSON.parse(iframe.contentWindow.name);
                    console.log(data);
                    iframe.contentWindow.document.write('');
                    iframe.contentWindow.close();
                  document.body.removeChild(iframe);
                } else if(state === 0) {
                    state = 1;
                    iframe.contentWindow.location = 'http://localhost:81/cross-domain/proxy.html';
                }
              };
          
              iframe.src = 'http://localhost:8080/data.php';
              document.body.appendChild(iframe);
            </script>
          </body>
          

          所以如上,我們就拿到了服務器返回的數據,但是有幾個條件是必不可少的:

          • iframe標簽的跨域能力
          • window.names屬性值在文檔刷新后依然存在的能力

          location.hash + iframe 跨域

          此跨域方法和上面介紹的比較類似,一樣是動態插入一個iframe然后設置其src為服務端地址,而服務端同樣輸出一端js代碼,也同時通過與子窗口之間的通信來完成數據的傳輸。

          關于錨點相信大家都已經知道了,其實就是設置錨點,讓文檔指定的相應的位置。錨點的設置用a標簽,然后href指向要跳轉到的id,當然,前提是你得有個滾動條,不然也不好滾動嘛是吧。

          而location.hash其實就是url的錨點。比如http://www.nealyang.cn#Nealyang的網址打開后,在控制臺輸入location.hash就會返回#Nealyang的字段。

          基礎知識補充完畢,下面我們來說下如何實現跨域

          如果index頁面要獲取遠端服務器的數據,動態的插入一個iframe,將iframe的src執行服務器的地址,這時候的top window 和包裹這個iframe的子窗口是不能通信的,因為同源策略,所以改變子窗口的路徑就可以了,將數據當做改變后的路徑的hash值加載路徑上,然后就可以通信了。將數據加在index頁面地址的hash上,
          index頁面監聽hash的變化,h5的hashchange方法

          <body>
            <script type="text/javascript">
              function getData(url, fn) {
                var iframe = document.createElement('iframe');
                iframe.style.display = 'none';
                iframe.src = url;
          
                iframe.onload = function() {
                  fn(iframe.contentWindow.location.hash.substring(1));
                  window.location.hash = '';
                  document.body.removeChild(iframe);
                };
          
                document.body.appendChild(iframe);
              }
          
              // get data from server
              var url = 'http://localhost:8080/data.php';
              getData(url, function(data) {
                var jsondata = JSON.parse(data);
                console.log(jsondata.name + ' ' + jsondata.age);
              });
            </script>
          </body>
          

          補充說明:其實location.hash和window.name都是差不多的,都是利用全局對象屬性的方法,然后這兩種方法和jsonp也是一樣的,就是只能夠實現get請求

          postMessage跨域

          這是由H5提出來的一個炫酷的API,IE8+,chrome,ff都已經支持實現了這個功能。這個功能也是非常的簡單,其中包括接受信息的Message時間,和發送信息的postMessage方法。

          發送信息的postMessage方法是向外界窗口發送信息

          otherWindow.postMessage(message,targetOrigin);
          

          otherWindow指的是目標窗口,也就是要給哪一個window發送消息,是window.frames屬性的成員或者是window.open方法創建的窗口。
          Message是要發送的消息,類型為String,Object(IE8、9不支持Obj),targetOrigin是限定消息接受范圍,不限制就用星號 *

          接受信息的message事件

          var onmessage = function(event) {
            var data = event.data;
            var origin = event.origin;
          }
          
          if(typeof window.addEventListener != 'undefined'){
              window.addEventListener('message',onmessage,false);
          }else if(typeof window.attachEvent != 'undefined'){
              window.attachEvent('onmessage', onmessage);
          }
          

          舉個例子

          a.html(http://www.nealyang.cn/a.html)

          <iframe id="iframe" src="http://www.neal.cn/b.html" style="display:none;"></iframe>
          <script>       
              var iframe = document.getElementById('iframe');
              iframe.onload = function() {
                  var data = {
                      name: 'aym'
                  };
                  // 向neal傳送跨域數據
                  iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.neal.cn');
              };
          
              // 接受domain2返回數據
              window.addEventListener('message', function(e) {
                  alert('data from neal ---> ' + e.data);
              }, false);
          </script>
          

          b.html(http://www.neal.cn/b.html)

          <script>
              // 接收domain1的數據
              window.addEventListener('message', function(e) {
                  alert('data from nealyang ---> ' + e.data);
          
                  var data = JSON.parse(e.data);
                  if (data) {
                      data.number = 16;
          
                      // 處理后再發回nealyang
                      window.parent.postMessage(JSON.stringify(data), 'http://www.nealyang.cn');
                  }
              }, false);
          </script>
          

          跨域資源共享 CORS

          因為是目前主流的跨域解決方案。所以這里多介紹點。

          簡介

          CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。
          它允許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。

          CORS需要瀏覽器和服務器同時支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。IE8+:IE8/9需要使用XDomainRequest對象來支持CORS。

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

          兩種請求

          說起來很搞笑,分為兩種請求,一種是簡單請求,另一種是非簡單請求。只要滿足下面條件就是簡單請求

          • 請求方式為HEAD、POST 或者 GET
          • http頭信息不超出以下字段:Accept、Accept-Language 、 Content-Language、 Last-Event-ID、 Content-Type(限于三個值:application/x-www-form-urlencoded、multipart/form-data、text/plain)

          為什么要分為簡單請求和非簡單請求,因為瀏覽器對這兩種請求方式的處理方式是不同的。

          簡單請求

          基本流程

          對于簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,增加一個Origin字段。
          下面是一個例子,瀏覽器發現這次跨源AJAX請求是簡單請求,就自動在頭信息之中,添加一個Origin字段。

          GET /cors HTTP/1.1
          Origin: http://api.bob.com
          Host: api.alice.com
          Accept-Language: en-US
          Connection: keep-alive
          User-Agent: Mozilla/5.0
          ...
          

          Origin字段用來說明,本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否同意這次請求。

          如果Origin指定的源,不在許可范圍內,服務器會返回一個正常的HTTP回應。
          瀏覽器發現,這個回應的頭信息沒有包含Access-Control-Allow-Origin字段(詳見下文),就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。

          注意,這種錯誤無法通過狀態碼識別,因為HTTP回應的狀態碼有可能是200。

          如果Origin指定的域名在許可范圍內,服務器返回的響應,會多出幾個頭信息字段。

             Access-Control-Allow-Origin: http://api.bob.com
             Access-Control-Allow-Credentials: true
             Access-Control-Expose-Headers: FooBar
             Content-Type: text/html; charset=utf-8
          

          上面的頭信息之中,有三個與CORS請求相關的字段,都以Access-Control-開頭

          • Access-Control-Allow-Origin :該字段是必須的。它的值要么是請求時Origin字段的值,要么是一個*,表示接受任意域名的請求
          • Access-Control-Allow-Credentials: 該字段可選。它的值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為true,即表示服務器明確許可,Cookie可以包含在請求中,一起發給服務器。這個值也只能設為true,如果服務器不要瀏覽器發送Cookie,刪除該字段即可。
          • Access-Control-Expose-Headers:該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必須在Access-Control-Expose-Headers里面指定。

          withCredentials 屬性

          上面說到,CORS請求默認不發送Cookie和HTTP認證信息。如果要把Cookie發到服務器,一方面要服務器同意,指定Access-Control-Allow-Credentials字段。

          另一方面,開發者必須在AJAX請求中打開withCredentials屬性。

          var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
          
          // 前端設置是否帶cookie
          xhr.withCredentials = true;
          
          xhr.open('post', 'http://www.domain2.com:8080/login', true);
          xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
          xhr.send('user=admin');
          
          xhr.onreadystatechange = function() {
              if (xhr.readyState == 4 && xhr.status == 200) {
                  alert(xhr.responseText);
              }
          };
          
          // jquery
          $.ajax({
              ...
             xhrFields: {
                 withCredentials: true    // 前端設置是否帶cookie
             },
             crossDomain: true,   // 會讓請求頭中包含跨域的額外信息,但不會含cookie
              ...
          });
          

          否則,即使服務器同意發送Cookie,瀏覽器也不會發送。或者,服務器要求設置Cookie,瀏覽器也不會處理。
          但是,如果省略withCredentials設置,有的瀏覽器還是會一起發送Cookie。這時,可以顯式關閉withCredentials。

          需要注意的是,如果要發送Cookie,Access-Control-Allow-Origin就不能設為星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie才會上傳,其他域名的Cookie并不會上傳,且(跨源)原網頁代碼中的document.cookie也無法讀取服務器域名下的Cookie。

          非簡單請求

          非簡單請求是那種對服務器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。

          非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。

          瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答復,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。

          var url = 'http://api.alice.com/cors';
          var xhr = new XMLHttpRequest();
          xhr.open('PUT', url, true);
          xhr.setRequestHeader('X-Custom-Header', 'value');
          xhr.send();
          

          瀏覽器發現,這是一個非簡單請求,就自動發出一個"預檢"請求,要求服務器確認可以這樣請求。下面是這個"預檢"請求的HTTP頭信息。

              OPTIONS /cors HTTP/1.1
             Origin: http://api.bob.com
             Access-Control-Request-Method: PUT
             Access-Control-Request-Headers: X-Custom-Header
             Host: api.alice.com
             Accept-Language: en-US
             Connection: keep-alive
             User-Agent: Mozilla/5.0...
          

          "預檢"請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。頭信息里面,關鍵字段是Origin,表示請求來自哪個源。

          除了Origin字段,"預檢"請求的頭信息包括兩個特殊字段。

          • Access-Control-Request-Method:該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。
          • Access-Control-Request-Headers:該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段,上例是X-Custom-Header

          預檢請求的回應

          服務器收到"預檢"請求以后,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,確認允許跨源請求,就可以做出回應

          HTTP/1.1 200 OK
          Date: Mon, 01 Dec 2008 01:15:39 GMT
          Server: Apache/2.0.61 (Unix)
          Access-Control-Allow-Origin: http://api.bob.com
          Access-Control-Allow-Methods: GET, POST, PUT
          Access-Control-Allow-Headers: X-Custom-Header
          Content-Type: text/html; charset=utf-8
          Content-Encoding: gzip
          Content-Length: 0
          Keep-Alive: timeout=2, max=100
          Connection: Keep-Alive
          Content-Type: text/plain
          

          上面的HTTP回應中,關鍵的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以請求數據。該字段也可以設為星號,表示同意任意跨源請求。

          如果瀏覽器否定了"預檢"請求,會返回一個正常的HTTP回應,但是沒有任何CORS相關的頭信息字段。這時,瀏覽器就會認定,服務器不同意預檢請求,因此觸發一個錯誤,被XMLHttpRequest對象的onerror回調函數捕獲。控制臺會打印出如下的報錯信息。

          服務器回應的其他CORS相關字段如下:

          Access-Control-Allow-Methods: GET, POST, PUT
          Access-Control-Allow-Headers: X-Custom-Header
          Access-Control-Allow-Credentials: true
          Access-Control-Max-Age: 1728000
          
          • Access-Control-Allow-Methods:該字段必需,它的值是逗號分隔的一個字符串,表明服務器支持的所有跨域請求的方法。注意,返回的是所有支持的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次"預檢"請求。
          • Access-Control-Allow-Headers:如果瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,表明服務器支持的所有頭信息字段,不限于瀏覽器在"預檢"中請求的字段。
          • Access-Control-Allow-Credentials:該字段與簡單請求時的含義相同。
          • Access-Control-Max-Age:該字段可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是20天(1728000秒),即允許緩存該條回應1728000秒(即20天),在此期間,不用發出另一條預檢請求。

          瀏覽器正常請求回應

          一旦服務器通過了"預檢"請求,以后每次瀏覽器正常的CORS請求,就都跟簡單請求一樣,會有一個Origin頭信息字段。服務器的回應,也都會有一個Access-Control-Allow-Origin頭信息字段。

          PUT /cors HTTP/1.1
          Origin: http://api.bob.com
          Host: api.alice.com
          X-Custom-Header: value
          Accept-Language: en-US
          Connection: keep-alive
          User-Agent: Mozilla/5.0...
          

          瀏覽器的正常CORS請求。上面頭信息的Origin字段是瀏覽器自動添加的。下面是服務器正常的回應。

          Access-Control-Allow-Origin: http://api.bob.com
          Content-Type: text/html; charset=utf-8
          

          Access-Control-Allow-Origin字段是每次回應都必定包含的

          結束語

          CORS與JSONP的使用目的相同,但是比JSONP更強大。JSONP只支持GET請求,CORS支持所有類型的HTTP請求。JSONP的優勢在于支持老式瀏覽器,以及可以向不支持CORS的網站請求數據。

          WebSocket協議跨域

          WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信,同時允許跨域通訊,是server push技術的一種很好的實現。

          原生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>
          

          node Server

          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.'); 
              });
          });
          

          node代理跨域

          node中間件實現跨域代理,是通過啟一個代理服務器,實現數據的轉發,也可以通過設置cookieDomainRewrite參數修改響應頭中cookie中域名,實現當前域的cookie寫入,方便接口登錄認證。

          利用node + express + http-proxy-middleware搭建一個proxy服務器

          前端代碼

          var xhr = new XMLHttpRequest();
          
          // 前端開關:瀏覽器是否讀寫cookie
          xhr.withCredentials = true;
          
          // 訪問http-proxy-middleware代理服務器
          xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
          xhr.send();
          

          后端代碼

          var express = require('express');
          var proxy = require('http-proxy-middleware');
          var app = express();
          
          app.use('/', proxy({
              // 代理跨域目標接口
              target: 'http://www.domain2.com:8080',
              changeOrigin: true,
          
              // 修改響應頭信息,實現跨域并允許帶cookie
              onProxyRes: function(proxyRes, req, res) {
                  res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
                  res.header('Access-Control-Allow-Credentials', 'true');
              },
          
              // 修改響應信息中的cookie域名
              cookieDomainRewrite: 'www.domain1.com'  // 可以為false,表示不修改
          }));
          
          app.listen(3000);
          console.log('Proxy server is listen at port 3000...');
          

          nginx代理跨域

          NGINX其實個人玩的不是太多,所以這里不能誤人子弟。簡單的說,咱們可以通過 Nginx 轉發請求,把跨域的接口寫成調本域的接口,然后將這些接口轉發到真正的請求地址上。具體 Nginx 的配置說明,可以參看后續我整理的 Nginx 教程說明。

          參考文檔

          http://www.ruanyifeng.com/blog/2016/04/cors.html
          https://segmentfault.com/a/1190000011145364

          一、什么是跨域?

          在日常開發過程中,經常要處理一些跨域問題,那么在了解跨域之前,首先要知道什么是同源策略(same-origin policy)。簡單來講同源策略就是瀏覽器為了保證用戶信息的安全,防止惡意的網站竊取數據,禁止不同域之間的JS進行交互。對于瀏覽器而言只要域名、協議、端口其中一個不同就會引發同源策略,從而限制他們之間如下的交互行為:

          1.Cookie、LocalStorage和IndexDB無法讀取;

          2.DOM無法獲得;

          3.AJAX請求不能發送。

          跨域的嚴格一點的定義是:只要協議,域名,端口有任何一個的不同,就被當作是跨域。

          如下表所示:

          特別注意兩點:

          第一,如果是協議和端口造成的跨域問題“前臺”是無能為力的,

          第二:在跨域問題上,域僅僅是通過“URL的首部”來識別而不會去嘗試判斷相同的ip地址對應著兩個域或兩個域是否在同一個ip上。

          “URL的首部”指window.location.protocol +window.location.host,也可以理解為“Domains, protocols and ports must match”。


          二 為什么瀏覽器要限制跨域訪問呢?

          原因就是安全問題:如果一個網頁可以隨意地訪問另外一個網站的資源,那么就有可能在客戶完全不知情的情況下出現安全問題。比如下面的操作就有安全問題:

          1.用戶訪問www.mybank.com,登陸并進行網銀操作,這時cookie啥的都生成并存放在瀏覽器;

          2.用戶突然想起件事,并迷迷糊糊的訪問了一個邪惡的網站www.xiee.com;

          3.這時該網站就可以在它的頁面中,拿到銀行的cookie,比如用戶名,登陸token等,然后發起對www.mybank.com的操作;

          4.如果這時瀏覽器不予限制,并且銀行也沒有做響應的安全處理的話,那么用戶的信息有可能就這么泄露了。


          三 為什么要跨域?

          既然有安全問題,那為什么又要跨域呢? 有時公司內部有多個不同的子域,比如一個是location.company.com ,而應用是放在app.company.com , 這時想從 app.company.com 去訪問 location.company.com 的資源就屬于跨域。


          四 解決跨域問題的方法:

          1.跨域資源共享(CORS)

          CORS(Cross-Origin Resource Sharing)跨域資源共享,定義了必須在訪問跨域資源時,瀏覽器與服務器應該如何溝通。CORS背后的基本思想就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功還是失敗。

          服務器端對于CORS的支持,主要就是通過設置Access-Control-Allow-Origin來進行的。如果瀏覽器檢測到相應的設置,就可以允許Ajax進行跨域的訪問。

          只需要在后臺中加上響應頭來允許域請求!在被請求的Response header中加入以下設置,就可以實現跨域訪問了!

          如下所示:

          2.通過jsonp跨域

          JSONP是JSON with Padding(填充式json)的簡寫,是應用JSON的一種新方法,只不過是被包含在函數調用中的JSON,例如:

          JSONP由兩部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數,而數據就是傳入回調函數中的JSON數據。

          JSONP的原理:通過script標簽引入一個js文件,這個js文件載入成功后會執行我們在url參數中指定的函數,并且會把我們需要的json數據作為參數傳入。所以jsonp是需要服務器端的頁面進行相應的配合的。(即用JavaScript動態加載一個script文件,同時定義一個callback函數給script執行而已。)

          在js中,我們直接用XMLHttpRequest請求不同域上的數據時,是不可以的。但是,在頁面上引入不同域上的js腳本文件卻是可以的,jsonp正是利用這個特性來實現的。 例如:有個a.html頁面,它里面的代碼需要利用ajax獲取一個不同域上的json數據,假設這個json數據地址是http://example.com/data.php,那么a.html中的代碼就可以這樣:

          js文件載入成功后會執行我們在url參數中指定的函數,并且會把我們需要的json數據作為參數傳入。所以jsonp是需要服務器端的頁面進行相應的配合的。

          最終,輸出結果為:dosomething(['a','b','c']);

          如果你的頁面使用jquery,那么通過它封裝的方法就能很方便的來進行jsonp操作了。jquery會自動生成一個全局函數來替換callback=?中的問號,之后獲取到數據后又會自動銷毀,實際上就是起一個臨時代理函數的作用。$.getJSON方法會自動判斷是否跨域,不跨域的話,就調用普通的ajax方法;跨域的話,則會以異步加載js文件的形式來調用jsonp的回調函數。

          JSONP的優缺點:

          JSONP的優點是:它不像XMLHttpRequest對象實現的Ajax請求那樣受到同源策略的限制;它的兼容性更好,在更加古老的瀏覽器中都可以運行,不需要XMLHttpRequest或ActiveX的支持;并且在請求完畢后可以通過調用callback的方式回傳結果。

          JSONP的缺點則是:它只支持GET請求而不支持POST等其它類型的HTTP請求;它只支持跨域HTTP請求這種情況,不能解決不同域的兩個頁面之間如何進行JavaScript調用的問題。

          CORS和JSONP對比:

          CORS與JSONP相比,無疑更為先進、方便和可靠。

          (1)JSONP只能實現GET請求,而CORS支持所有類型的HTTP請求;

          (2)使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得說句,比起JSONP有更好的錯誤處理;

          (3)JSONP主要被老的瀏覽器支持,它們往往不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS;

          3.通過修改document.domain來跨子域

          上面的jsonp是來解決ajax跨域請求的,那么如果是需要處理 Cookie 和 iframe 該怎么辦呢?這時候就可以通過修改document.domain來跨子域。兩個網頁一級域名相同,只是二級域名不同,瀏覽器允許通過設置document.domain共享 Cookie或者處理iframe。比如A網頁是http://w1.example.com/a.html,B網頁是http://w2.example.com/b.html,那么只要設置相同的document.domain,兩個網頁就可以共享Cookie。

          注意,這種方法只適用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 無法通過這種方法,規避同源政策,而要使用下文介紹的PostMessage API。

          另外,服務器也可以在設置Cookie的時候,指定Cookie的所屬域名為一級域名,比如.example.com。

          不同的iframe 之間(父子或同輩),是能夠獲取到彼此的window對象的,但是你卻不能使用獲取到的window對象的屬性和方法(html5中的postMessage方法是一個例外,還有些瀏覽器比如ie6也可以使用top、parent等少數幾個屬性),總之,你可以當做是只能獲取到一個幾乎無用的window對象。

          首先說明一下同域之間的iframe是可以操作的。比如http://127.0.0.1/JSONP/a.html里面嵌入一個iframe指向http://127.0.0.1/myPHP/b.html。那么在a.html里面是可以操作iframe里面的DOM的。

          如果兩個網頁不同源,就無法拿到對方的DOM。典型的例子是iframe窗口和window.open方法打開的窗口,它們與父窗口無法通信。如果兩個窗口一級域名相同,只是二級域名不同,那么document.domain屬性,就可以規避同源政策,拿到DOM。

          4.使用window.name來進行跨域

          window對象有個name屬性,該屬性有個特征:即在一個窗口(window)的生命周期內,窗口載入的所有的頁面都是共享一個window.name的,每個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的所有頁面中的,并不會因新頁面的載入而進行重置。這個屬性的最大特點是,無論是否同源,只要在同一個窗口里,前一個網頁設置了這個屬性,后一個網頁可以讀取它。

          比如:有一個頁面a.html,它里面有這樣的代碼:

          b.html頁面的代碼:

          載入后1秒,跳轉到了b.html頁面,結果b頁面打印出了:

          可以看到在b.html頁面上成功獲取到了它的上一個頁面a.html給window.name設置的值。如果在之后所有載入的頁面都沒對window.name進行修改的話,那么所有這些頁面獲取到的window.name的值都是a.html頁面設置的那個值。當然,如果有需要,其中的任何一個頁面都可以對window.name的值進行修改。注意,window.name的值只能是字符串的形式,這個字符串的大小最大能允許2M左右甚至更大的一個容量,具體取決于不同的瀏覽器,但一般是夠用了。

          利用window.name可以對同域或者不同域的之間的js進行交互。

          那么在a.html頁面中,我們怎么把b.html頁面載入進來呢?顯然我們不能直接在a.html頁面中通過改變window.location來載入b.html頁面,因為我們想要即使a.html頁面不跳轉也能得到b.html里的數據。答案就是在a.html頁面中使用一個隱藏的iframe來充當一個中間人角色,由iframe去獲取b.html的數據,然后a.html再去得到iframe獲取到的數據。

          5.使用HTML5的window.postMessage方法跨域

          上面兩種方法都屬于破解,HTML5為了解決這個問題,引入了一個全新的API:跨文檔通信 API(Cross-document messaging)。

          這個API為window對象新增了一個window.postMessage方法,允許跨窗口通信,不論這兩個窗口是否同源。目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支持window.postMessage方法。

          舉例來說,父窗口http://a.com向子窗口http://b.com發消息,調用postMessage方法就可以了。

          a頁面:

          b頁面通過監聽message事件可以接受到來自a頁面的消息。

          子窗口向父窗口發送消息的寫法類似。

          通過window.postMessage,讀寫其他窗口的 LocalStorage 也成為了可能。

          下面是一個例子,主窗口寫入iframe子窗口的localStorage。

          父窗口發送消息代碼:

          子窗口接收消息的代碼:

          6.通過WebSocket進行跨域

          web sockets是一種瀏覽器的API,它的目標是在一個單獨的持久連接上提供全雙工、雙向通信。(同源策略對web sockets不適用)

          web sockets原理:在js創建了web socket之后,會有一個HTTP請求發送到瀏覽器以發起連接。取得服務器響應后,建立的連接會使用HTTP升級從HTTP協議交換為web sockt協議。

          只有在支持web socket協議的服務器上才能正常工作。

          7.圖像ping(單向)

          什么是圖像ping: 圖像ping是與服務器進行簡單、單向的跨域通信的一種方式,請求的數據是通過查詢字符串的形式發送的,而相應可以是任意內容,但通常是像素圖或204相應(No Content)。 圖像ping有兩個主要缺點:首先就是只能發送get請求,其次就是無法訪問服務器的響應文本。

          使用方法:

          然后頁面上就可以顯示我放在我的github上某個地方的照片啦。

          與<img>類似的可以跨域內嵌資源的還有:

          (1)<script src=""></script>標簽嵌入跨域腳本。語法錯誤信息只能在同源腳本中捕捉到。上面jsonp也用到了呢。

          (2) <link src="">標簽嵌入CSS。由于CSS的松散的語法規則,CSS的跨域需要一個設置正確的Content-Type消息頭。不同瀏覽器有不同的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。

          (3)<video> 和 <audio>嵌入多媒體資源。

          (4)<object>, <embed> 和 <applet>的插件。

          (5)@font-face引入的字體。一些瀏覽器允許跨域字體( cross-origin fonts),一些需要同源字體(same-origin fonts)。

          (6) <frame> 和 <iframe>載入的任何資源。站點可以使用X-Frame-Options消息頭來阻止這種形式的跨域交互。

          8.使用片段識別符來進行跨域

          片段標識符(fragment identifier)指的是,URL的#號后面的部分,比如http://example.com/x.html#flag的#flag。如果只是改變片段標識符,頁面不會重新刷新。

          父窗口可以把信息,寫入子窗口的片段標識符。在父窗口寫入:

          子窗口通過監聽hashchange事件得到通知。

          同樣的,子窗口也可以改變父窗口的片段標識符。



          (技術內容來自:比格云-小鹿鹿)


          主站蜘蛛池模板: 久久精品无码一区二区三区免费 | 中文日韩字幕一区在线观看| 亚洲一区二区三区写真| 麻豆一区二区在我观看| 中文字幕av日韩精品一区二区| 亚洲欧洲一区二区| 国产精品一区二区久久精品涩爱| 日本一区二区三区在线观看视频| 亚洲国产成人久久综合一区77| 国产一区二区三区在线观看精品| 色偷偷av一区二区三区| 无码中文字幕人妻在线一区二区三区 | 日韩美女在线观看一区| 一区二区三区电影网| 色窝窝无码一区二区三区色欲| 亚洲字幕AV一区二区三区四区 | 中文乱码精品一区二区三区| 精品永久久福利一区二区| 波多野结衣av高清一区二区三区| 亚洲一区在线视频| 竹菊影视欧美日韩一区二区三区四区五区| 亚洲av色香蕉一区二区三区蜜桃| 色狠狠色狠狠综合一区| 福利电影一区二区| 亚洲AV无码一区二区二三区入口| 国产韩国精品一区二区三区久久 | 亚洲AV美女一区二区三区| 成人免费一区二区无码视频| 日韩人妻无码一区二区三区 | 国产美女露脸口爆吞精一区二区| 91精品一区二区综合在线| 亚洲AV日韩精品一区二区三区| 亚洲午夜电影一区二区三区| 中文字幕亚洲一区二区三区| 国产精品无码一区二区在线| 久久91精品国产一区二区| 福利视频一区二区牛牛| AV天堂午夜精品一区二区三区| 久久国产精品一区免费下载| 乱中年女人伦av一区二区| 成人精品一区二区电影|