跨域

一响迂、同源策略(Same origin Policy)

瀏覽器出于安全方面的考慮,只允許與本域下的接口交互渗勘。不同源的客戶端腳本在沒有明確授權(quán)的情況下沐绒,不能讀寫對方的資源。

1旺坠、同源(本域)

所謂“同源”指的是”三個相同“乔遮。相同的域名、端口和協(xié)議取刃,這三個相同的話就視為同一個域蹋肮,本域下的JS腳本只能讀寫本域下的數(shù)據(jù)資源,無法訪問其它域的資源璧疗。

  • 協(xié)議相同 (都是http或者h(yuǎn)ttps)

  • 域名相同 (都是http://jirengu.com/ahttp://jirengu.com/b)

  • 端口相同(都是80端口坯辩,如果沒有寫端口,默認(rèn)是80端口)

舉個例子

  • 同源情況:

http://jirengu.com/a/b.jshttp://jirengu.com/index.php

  • 不同源情況:

http://jirengu.com/main.js> 和 https://jirengu.com/a.php (協(xié)議不同)

http://jirengu.com/main.js> 和 http://bbs.jirengu.com/a.php (域名不同崩侠,域名必須完全相同才可以)

http://jiengu.com/main.jshttp://jirengu.com:8080/a.php (端口不同,第一個是80)

2漆魔、Ajax 跨域報錯實例

(1)修改host文件:給host文件里添加兩條記錄,方便跨域操作

image

上圖意思是訪問 a.com 或是 b.com 相當(dāng)于訪問本機(jī), 可以實現(xiàn)一個場景:瀏覽器是a.com却音,而接口是b.com,雖說最終對應(yīng)的是本機(jī)改抡,但是域名不一樣

(2)建一個index.html文件,獲取數(shù)據(jù)

<h1>hello world</h1>
<script>
  var xhr = new XMLHttpRequest()
  xhr.open('GET','http://localhost:8080/getWeather', true)
  xhr.send()
  xhr.onload = function(){
    console.log(xhr.responseText)
  }
</script>

(3)建一個server.js文件系瓢,實現(xiàn)靜態(tài)文件雀摘、動態(tài)路由功能

var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')

http.createServer(function(req, res){

  var pathObj = url.parse(req.url, true)

  switch (pathObj.pathname) {
    case '/getWeather':
      res.end(JSON.stringify({beijing: 'sunny'}))
      break;

    default:
      fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
        if(e){
          res.writeHead(404, 'not found')
          res.end('<h1>404 Not Found</h1>')
        }else{
          res.end(data)
        }
      }) 
  }
}).listen(8080)

(4)執(zhí)行結(jié)果

打開終端,cd 到當(dāng)前文件夾八拱,然后輸入node server.js,啟動靜態(tài)服務(wù)器

A、瀏覽器地址欄輸入localhost:8080/index.html ,獲取到了數(shù)據(jù)

image

B肌稻、瀏覽器地址改為127.0.0.1:8080/index.html,就報錯啦清蚀,因為當(dāng)前域名和請求域名不一樣

image

C、如果瀏覽器改成a.com或是b.com 也是會報錯的爹谭,就算它們都是指向同一個本機(jī)

總結(jié)一下:只有在請求域名和當(dāng)前域名相同的情況下枷邪,才能獲取數(shù)據(jù),才不會報錯诺凡。

注:跨域的資源內(nèi)嵌是被允許的东揣,對于當(dāng)前頁面來說頁面存放的 JS 文件的域不重要,重要的是加載該 JS 頁面所在什么域

二腹泌、跨域

解決同源策略帶來的不便嘶卧,突破同源策略的限制去獲取不同源之間的數(shù)據(jù)信息或者進(jìn)行不同源之間的信息傳遞。

解決辦法

1? JSONP

HTML 中 script 標(biāo)簽可以加載其他域下的js凉袱,比如我們經(jīng)常引入一個其他域下線上cdn的jQuery芥吟。

可以這樣子實現(xiàn)從其他域下獲取數(shù)據(jù)

<script src="http://api.jirengu.com/weather.php"></script>

這時候會向天氣接口發(fā)送請求獲取數(shù)據(jù),獲取數(shù)據(jù)后做為 js 來執(zhí)行专甩。 但這里有個問題钟鸵, 數(shù)據(jù)是 JSON 格式的數(shù)據(jù),直接作為 JS 運(yùn)行的話,如何去得到這個數(shù)據(jù)來操作呢涤躲?

此時需要后端的配合棺耍,因為后端的接口需要根據(jù)約定的參數(shù)獲取回調(diào)函數(shù)名,然后跟返回數(shù)據(jù)進(jìn)行拼接种樱,最后進(jìn)行響應(yīng)

<script src="http://api.jirengu.com/weather.php?callback=showData"></script>

這個請求到達(dá)后端后蒙袍,后端會去解析callback這個參數(shù)獲取到字符串showData,在發(fā)送數(shù)據(jù)做如下處理:

之前后端返回數(shù)據(jù): {"city": "hangzhou", "weather": "晴天"}
現(xiàn)在后端返回數(shù)據(jù): showData({"city": "hangzhou", "weather": "晴天"})

前端script標(biāo)簽在加載數(shù)據(jù)后會把 「showData({“city”: “hangzhou”, “weather”: “晴天”})」做為 js 來執(zhí)行缸托。

這實際上就是調(diào)用showData這個函數(shù)左敌,同時參數(shù)是 {“city”: “hangzhou”, “weather”: “晴天”}。

當(dāng)然前端提前在頁面定義好showData這個全局函數(shù)俐镐,在函數(shù)內(nèi)部處理參數(shù)即可矫限。

<script>
function showData(ret){
console.log(ret);
}
</script>
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
總結(jié)一下:

(1)JSONP是通過 script 標(biāo)簽加載數(shù)據(jù)的方式去獲取數(shù)據(jù)當(dāng)做 JS 代碼來執(zhí)行

(2)提前在頁面上聲明一個函數(shù),函數(shù)名通過接口傳參的方式傳給后臺佩抹,后臺解析到函數(shù)名后在原始數(shù)據(jù)上「包裹」這個函數(shù)名叼风,發(fā)送給前端。

換句話說棍苹,JSONP 需要對應(yīng)接口的后端的配合才能實現(xiàn)

舉個例子

server.js 文件

var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')

http.createServer(function(req, res){
  var pathObj = url.parse(req.url, true)

  switch (pathObj.pathname) {
    case '/getNews':
      var news = [
        "第11日前瞻:中國沖擊4金 博爾特再戰(zhàn)200米羽球",
        "正直播柴飚/洪煒出戰(zhàn) 男雙力爭會師決賽",
        "女排將死磕巴西无宿!郎平安排男陪練模仿對方核心"
        ]
      res.setHeader('Content-Type','text/json; charset=utf-8')
      if(pathObj.query.callback){
        res.end(pathObj.query.callback + '(' + JSON.stringify(news) + ')')
      }else{
        res.end(JSON.stringify(news))
      }

      break;

    default:
      fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
        if(e){
          res.writeHead(404, 'not found')
          res.end('<h1>404 Not Found</h1>')
        }else{
          res.end(data)
        }
      }) 
  }
}).listen(8080)

html文件

<!DOCTYPE html>
<html>
<body>
  <div class="container">
    <ul class="news">
    </ul>
    <button class="show">show news</button>
  </div>

<script>

  $('.show').addEventListener('click', function(){
    var script = document.createElement('script');
    script.src = 'http://127.0.0.1:8080/getNews?callback=appendHtml';
    document.head.appendChild(script); 
   //script標(biāo)簽放到head上,并向http://127.0.0.1:8080/getNews?callback=appendHtml發(fā)請求,獲取到     并執(zhí)行
    document.head.removeChild(script);// 為了顯示好看枢里,加上移除這句
  })

  function appendHtml(news){
    var html = '';
    for( var i=0; i<news.length; i++){
      html += '<li>' + news[i] + '</li>';
    }
    console.log(html);
    $('.news').innerHTML = html;
  }

  function $(id){
    return document.querySelector(id);
  }
</script>
</html>

打開終端孽鸡,cd 到當(dāng)前文件夾蹂午,然后輸入node server.js,啟動靜態(tài)服務(wù)器

顯示結(jié)果:

image

圖中Request URL :http://127.0.0.1:8080/getNews?callback=appendHtml 會向瀏覽器發(fā)請求彬碱,得到數(shù)據(jù),然后當(dāng)成 js 去執(zhí)行豆胸,因為頁面上已經(jīng)有了appendHtml函數(shù),然后去執(zhí)行這個函數(shù),把a(bǔ)ppendHtml 括號里的內(nèi)容作為參數(shù)傳遞進(jìn)去

image

2巷疼、CORS

全稱是跨域資源共享(Cross-Origin Resource Sharing)晚胡,是一種 ajax 跨域請求資源的方式,支持現(xiàn)代瀏覽器嚼沿,IE支持10以上估盘。

實現(xiàn)方式:

當(dāng)你使用 XMLHttpRequest 發(fā)送請求時,瀏覽器發(fā)現(xiàn)該請求不符合同源策略骡尽,會給該請求加一個請求頭:Origin遣妥,后臺進(jìn)行一系列處理:

(1)如果確定接受請求則在返回結(jié)果中加入一個響應(yīng)頭:Access-Control-Allow-Origin; 瀏覽器判斷該相應(yīng)頭中是否包含 Origin 的值。

(2)如果有則瀏覽器會處理響應(yīng)爆阶,我們就可以拿到響應(yīng)數(shù)據(jù)燥透。

(3)如果不包含瀏覽器直接駁回,這時我們無法拿到響應(yīng)數(shù)據(jù)辨图。

所以 CORS 的表象是讓你覺得它與同源的 ajax 請求沒啥區(qū)別班套,代碼完全一樣。

舉個例子

server.js文件

var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')

http.createServer(function(req, res){
  var pathObj = url.parse(req.url, true)

  switch (pathObj.pathname) {
    case '/getNews':
      var news = [
        "第11日前瞻:中國沖擊4金 博爾特再戰(zhàn)200米羽球",
        "正直播柴飚/洪煒出戰(zhàn) 男雙力爭會師決賽",
        "女排將死磕巴西故河!郎平安排男陪練模仿對方核心"
        ]

      res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
      //訪問控制允許的域 http://localhost:8080
      //res.setHeader('Access-Control-Allow-Origin','*')
      res.end(JSON.stringify(news))
      break;
    default:
      fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
        if(e){
          res.writeHead(404, 'not found')
          res.end('<h1>404 Not Found</h1>')
        }else{
          res.end(data)
        }
      }) 
  }
}).listen(8080)

index.html文件

<!DOCTYPE html>
<html>
<body>
  <div class="container">
    <ul class="news">

    </ul>
    <button class="show">show news</button>
  </div>

<script>

  $('.show').addEventListener('click', function(){
    var xhr = new XMLHttpRequest()
    xhr.open('GET', 'http://127.0.0.1:8080/getNews', true)
    xhr.send()
    xhr.onload = function(){
      appendHtml(JSON.parse(xhr.responseText))
    }
  })

  function appendHtml(news){
    var html = ''
    for( var i=0; i<news.length; i++){
      html += '<li>' + news[i] + '</li>'
    }
    $('.news').innerHTML = html
  }

  function $(selector){
    return document.querySelector(selector)
  }
</script>
</html>

顯示結(jié)果

(1)輸入http://127.0.0.1:8080/index.html 發(fā)送請求時吱韭,不需要跨域,請求頭什么都沒有

image

(2)輸入http://localhost:8080/index.html鱼的,此時出現(xiàn)跨域

響應(yīng)頭有Access-Control-Allow-Origin: http://localhost:8080

請求頭有Origin: http://localhost:8080

表示兩者相同理盆,可以獲取數(shù)據(jù)

[圖片上傳失敗...(image-fd73d2-1544271551949)]

(3)輸入http://a.com:8080/index.html 時,就報錯啦

image

因為服務(wù)端不允許a.com 獲取數(shù)據(jù)

image

響應(yīng)頭 Access-Control-Allow-Origin 設(shè)置為星號凑阶,表示同意任意跨源請求

那么http://a.com:8080/index.html就能獲取數(shù)據(jù)啦!

3?降域

(1)iframe 不同源

iframe里面加載的頁面猿规,它的域名,如果和我當(dāng)前的是屬于不同域,雖然iframe里面的東西可以加載宙橱,但外部的js無法去獲取或操作的姨俩。 也就是說,只有在相同域名下的iframe师郑,才可以去訪問里面的東西

舉個例子

a.html

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>

<style>
    .ct{
        width: 910px;
        margin: 0 auto;
    }
    .main{
        float: left;
        width: 450px;
        height: 300px;
        border: 1px solid #ccc;
    }
    .main input{
        margin: 20px;
        width: 200px;
    }
    .iframe{
        float: right;
    }
    iframe{
        width: 450px;
        height: 300px;
        border: 1px dashed #ccc;
    }
</style>

<body>
<div class="ct">
    <h1>使用降域?qū)崿F(xiàn)跨域</h1>
    <div class="main">
        <input type="text" placeholder="http://a.com:8080/a.html">
    </div>
    <iframe src="http://b.com:8080/b.html" frameborder="0"></iframe>
</div>

<script>
//URL  http://a.jrg.com:8080/a.html
document.querySelector('.main input').addEventListener('input',function(){
        console.log(this.value);
        window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "jrg.com"
</script>
</body>
</html>

b.html

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<style>
    html,body{
        margin: 0;
    }
    input{
        margin: 20px;
        width: 200px;
    }
</style>
<body>
<input id = "input" type="text" placeholder="http://b.jrg.com:8080/b.html">

<script>
// URL http://b.jrg.com:8080/b.html
document.querySelector('#input').addEventListener('input',function(){
    window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'jrg.com';
</script>
</body>
</html>

同樣先是host 文件添加兩條記錄

image

打開終端环葵,cd 到當(dāng)前文件夾,啟動http-server

用a.com 打開a.html, http://a.com:8080/a.html , 其中頁面iframe的地址是b.com宝冕,和網(wǎng)頁不同源的张遭。可以看到該frame可以正確加載地梨,但我們不能用js操作它

image

用b.com 打開a.html, http://b.com:8080/a.html , 其中frame 的地址是b.com菊卷,和網(wǎng)頁同源的缔恳,現(xiàn)在我們就可以用 js 獲取 iframe里面的內(nèi)容

image
(2)修改源 document.domain

當(dāng)兩個url的主域名不同,但子域名相同的情況下的烁,我們可以通過<iframe>標(biāo)簽將目標(biāo)url先嵌入html中褐耳,再設(shè)置頁面的document.domain值為兩個url共同的子域名,即可實現(xiàn)降域

注:兩個url的子域名必須相同

舉個例子

a.jrg.com:8080/a.html 和 b.jrg.com:8080/b.html 頁面代碼都加上document.domain = "jrg.com"

在a.html頁面中建一個iframe渴庆,通過iframe,兩個js文件即可交互數(shù)據(jù)

a.html

<div class="ct">
  <h1>使用降域?qū)崿F(xiàn)跨域</h1>
  <div class="main">
    <input type="text" placeholder="http://a.jrg.com:8080/a.html">
  </div>
  <iframe src="http://b.jrg.com:8080/b.html" frameborder="0" ></iframe>
</div>

<script>
//URL: http://a.jrg.com:8080/a.html
document.querySelector('.main input').addEventListener('input', function(){
  console.log(this.value);
  window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "jrg.com"
</script>

b.html

<script>
// URL: http://b.jrg.com:8080/b.html
document.querySelector('#input').addEventListener('input', function(){
    window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'jrg.com';
</script>

4?PostMessage

允許來自不同源的腳本采用異步方式進(jìn)行有限的通信雅镊,可以實現(xiàn)跨文本檔襟雷、多窗口、跨域消息傳遞仁烹。

舉個例子 (實現(xiàn)iframe跨域通信)

a.html

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>

<style>
    .ct{
        width: 910px;
        margin: 0 auto;
    }
    .main{
        float: left;
        width: 450px;
        height: 300px;
        border: 1px solid #ccc;
    }
    .main input{
        margin: 20px;
        width: 200px;
    }
    .iframe{
        float: right;
    }
    iframe{
        width: 450px;
        height: 300px;
        border: 1px dashed #ccc;
    }
</style>

<body>
    <div class="ct">
        <h1>使用postMessage實現(xiàn)跨域</h1>
    </div>
    <div class="main">
        <input type="text" placeholder="http://a.jrg.com:8080/a.html">
    </div>
    <iframe src="http://localhost:8080/b.html" frameborder="0"></iframe>

<script>
//URL http://a.jrg.com:8080/a.html

$('.main input').addEventListener('input',function(){
    console.log(this.value);
    window.frames[0].postMessage(this.value,'*');
    // a.html向跨域的iframe頁面http://localhost:8080/b.html傳遞數(shù)據(jù)
})

//監(jiān)聽有沒有數(shù)據(jù)發(fā)送過來
window.addEventListener('message',function(e){
    $('.main input').value = e.data
    console.log(e.data)
})
function $(id){
    return document.querySelector(id)
}
</script>
</body>
</html>

b.html

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<style>
    html,body{
        margin: 0;
    }

    input{
        margin: 20px;
        width: 200px;
    }
</style>
<body>
<input id="input" type="text" placeholder="http://b.jrg.com:8080/b.html">

<script>
// URL http://b.jrg.com:8080/b.html

$('#input').addEventListener('input',function(){
    window.parent.postMessage(this.value,'*')
})

window.addEventListener('message',function(e){
    $('#input').value = e.data
    console.log(e.data)
})

function $(id){
    return document.querySelector(id)
}
</script>
</body>
</html>

啟動http-server耸弄,查看執(zhí)行結(jié)果

image
image

總結(jié)一下:

A、a.html 通過 window.postMessage() 發(fā)送一個信息給b.html

B卓缰、b.html 在 window 上添加一個事件監(jiān)聽綁定 message 事件计呈,可以接收到來自任何不同域名通過 postMessage 方法發(fā)送過來的信息

C、當(dāng) b.html 接收到 a.html 發(fā)送過來的信息時執(zhí)行監(jiān)聽事件就 OK征唬,在監(jiān)聽事件的 event 參數(shù)中包含了所有 message 事件接收到的相關(guān)數(shù)據(jù)捌显。包括發(fā)送信息的內(nèi)容 event.data,發(fā)送信息的域名 event.origin 等等

同樣的总寒,在 a.html 內(nèi)添加一個事件監(jiān)聽綁定 message 事件扶歪,在 b.html 內(nèi)通過 postMessage方法發(fā)送信息給 a.html 一樣可以進(jìn)行跨域通信

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市摄闸,隨后出現(xiàn)的幾起案子善镰,更是在濱河造成了極大的恐慌,老刑警劉巖年枕,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炫欺,死亡現(xiàn)場離奇詭異,居然都是意外死亡熏兄,警方通過查閱死者的電腦和手機(jī)品洛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霍弹,“玉大人毫别,你說我怎么就攤上這事〉涓瘢” “怎么了岛宦?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長耍缴。 經(jīng)常有香客問我砾肺,道長挽霉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任变汪,我火速辦了婚禮侠坎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘裙盾。我一直安慰自己实胸,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布番官。 她就那樣靜靜地躺著庐完,像睡著了一般。 火紅的嫁衣襯著肌膚如雪徘熔。 梳的紋絲不亂的頭發(fā)上门躯,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音酷师,去河邊找鬼讶凉。 笑死,一個胖子當(dāng)著我的面吹牛山孔,可吹牛的內(nèi)容都是我干的懂讯。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼饱须,長吁一口氣:“原來是場噩夢啊……” “哼域醇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蓉媳,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤譬挚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后酪呻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體减宣,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年玩荠,在試婚紗的時候發(fā)現(xiàn)自己被綠了漆腌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡阶冈,死狀恐怖闷尿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情女坑,我是刑警寧澤填具,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響劳景,放射性物質(zhì)發(fā)生泄漏誉简。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一盟广、第九天 我趴在偏房一處隱蔽的房頂上張望闷串。 院中可真熱鬧,春花似錦筋量、人聲如沸烹吵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽年叮。三九已至,卻和暖如春玻募,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背一姿。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工七咧, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叮叹。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓艾栋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蛉顽。 傳聞我的和親對象是個殘疾皇子蝗砾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容