進(jìn)階13 JSONP 和 跨域

1. 跨域和同源

首先來(lái)看摘自MDN上對(duì)于跨域缸榛,較為標(biāo)準(zhǔn)的解釋:

當(dāng)一個(gè)資源從與該資源本身所在的服務(wù)器不同的域或端口請(qǐng)求一個(gè)資源時(shí)俐银,資源會(huì)發(fā)起一個(gè)跨域 HTTP 請(qǐng)求懈涛。
比如蔽莱,站點(diǎn) http://domain-a.com 的某 HTML 頁(yè)面通過(guò) <img> 的 src 請(qǐng)求 http://domain-b.com/image.jpg弟疆。網(wǎng)絡(luò)上的許多頁(yè)面都會(huì)加載來(lái)自不同域的CSS樣式表,圖像和腳本等資源碾褂。
出于安全考慮兽间,瀏覽器會(huì)限制從腳本內(nèi)發(fā)起的跨域HTTP請(qǐng)求。例如正塌,XMLHttpRequest 和 Fetch 遵循同源策略。因此恤溶,使用 XMLHttpRequest或 Fetch 的Web應(yīng)用程序如果不使用跨域技術(shù)乓诽,只能將HTTP請(qǐng)求發(fā)送到其自己的域。

同源策略(same-origin policy):
瀏覽器出于安全方面的考慮咒程,只允許與本域下的接口交互鸠天。不同源的客戶端腳本在沒有明確授權(quán)的情況下,不能讀寫對(duì)方的資源帐姻,防止惡意的網(wǎng)站竊取數(shù)據(jù)稠集、cookie等。

但不一定是瀏覽器限制了發(fā)起跨站請(qǐng)求饥瓷,也可能是跨站請(qǐng)求可以正常發(fā)起剥纷,但是返回結(jié)果被瀏覽器攔截了。

比如我現(xiàn)在用的Chrome/61.0.3163.91呢铆,雖然有同源策略的存在晦鞋,但是在調(diào)試工具的Network下,Status Code 200 OK, 說(shuō)明數(shù)據(jù)是返回回來(lái)了, 并且可以在Preview 或者 Response里看到數(shù)據(jù)悠垛。

什么才是同源:
字符串完全匹配才是同源线定,協(xié)議不同 域名不同(子域名和主域名并不是同源)端口不同,都不算是同源确买。

本地調(diào)試時(shí):
一個(gè)http-server服務(wù)器只能監(jiān)聽一個(gè)端口斤讥,監(jiān)聽多個(gè)可以設(shè)置不同端口,比如:
http-server -c-1 -p 80
http-server -c-1 -p 81

是跨域還是本域同源湾趾,看2個(gè)點(diǎn):

  1. 發(fā)送AJAX 請(qǐng)求的當(dāng)前頁(yè)面 URL 是什么
  2. AJAX 請(qǐng)求的 URL 是什么

這兩個(gè) URL 同源周偎,則不是跨域。

此外撑帖, <iframe> 標(biāo)簽蓉坎,也是受同源策略限制的。

2. 跨域的幾種方式

- JSONP

JSONP是服務(wù)器與客戶端跨源通信的常用方法胡嘿。最大特點(diǎn)就是簡(jiǎn)單適用蛉艾,老式瀏覽器全部支持,服務(wù)器改造非常小衷敌。

html中 <script> 標(biāo)簽可以引入其他域下的js勿侯,比如引入線上的jquery庫(kù)。利用這個(gè)特性缴罗,可實(shí)現(xiàn)跨域訪問(wèn)接口, 但是需要后端支持助琐。

首先引入標(biāo)簽,參數(shù)中指定回調(diào)函數(shù)名:

  <script src="http://weather.com.cn?city=hefei&callback=showWeather">

請(qǐng)求到的數(shù)據(jù)面氓, 由于受到后端支持兵钮,所以類似如下結(jié)構(gòu),后端把原始數(shù)據(jù)放在要執(zhí)行的函數(shù)的參數(shù)里面返回給前端:

    showWeather({
      "city": "hefei",
      weatheer: {xxx}
    })

當(dāng)數(shù)據(jù)返回到了客戶端舌界,由于是<script>標(biāo)簽掘譬,所以會(huì)自動(dòng)當(dāng)作js代碼執(zhí)行。

但是呻拌,我們雖然引入script標(biāo)簽時(shí)葱轩,在URL的中指定了callback的函數(shù)名,以便后端能用正確的函數(shù)名去‘包裝’藐握,可是這個(gè)傳入了原始數(shù)據(jù)的函數(shù)在我們的瀏覽器中運(yùn)行起來(lái)靴拱,到底要得到什么結(jié)果呢?

答案幾乎是顯而易見的猾普,就像非跨域請(qǐng)求一樣袜炕,我們需要對(duì)數(shù)據(jù)進(jìn)行處理,比如解析數(shù)據(jù)抬闷,進(jìn)行html的拼接妇蛀,并最終展示到頁(yè)面上耕突。

所以我們要聲明并完善這個(gè)callback函數(shù):

    function showWeather(json){
      // do something
    }

以上有個(gè)問(wèn)題,就是在引入script標(biāo)簽的時(shí)候评架,是直接提前寫在HTML文檔里的眷茁,而我們拿到數(shù)據(jù)處理完后(js執(zhí)行完畢后),這個(gè)標(biāo)簽就沒有用處了纵诞,而且實(shí)際場(chǎng)景中有多個(gè)類似請(qǐng)求的情況時(shí)上祈,如何才能保持HTML的干凈利索呢?

可以考慮下面的方式浙芙,使用js創(chuàng)建script標(biāo)簽登刺,引用到資源后,這里由于是立即并且逐條執(zhí)行的嗡呼,所以看似下一句立刻刪除了標(biāo)簽纸俭,但實(shí)際上中間已經(jīng)執(zhí)行過(guò)了我們起初定義的函數(shù)。

    document.querySelector('.change').addEventListener('click', function(){
      var script = document.createElement('script')
      script.src = 'http://127.0.0.1:8080/getNews?callback=appendHtml';
      document.head.appendChild(script)
      document.head.removeChild(script) //請(qǐng)求數(shù)據(jù)南窗,執(zhí)行完畢后揍很,就立即刪除script的引入標(biāo)簽
    })

而后端之前也提到了,只需要在同源請(qǐng)求中万伤,增加判斷語(yǔ)句窒悔,如果有callback參數(shù),則返回使用函數(shù)‘包裝’后的數(shù)據(jù):

  var cb = req.query.callback
  if(cb){
    res.send(cb + '(' + JSON.stringify(data) + ')')
  }else{
    res.send(data)
  }

點(diǎn)擊查看完整實(shí)例代碼

敌买!注意演示中简珠,使用了 server-mock 工具,使用隨同NodeJS一起安裝的包管理工具NPM進(jìn)行server-mock的安裝虹钮,然后把index.html 和router.js 放在一個(gè)文件夾聋庵,接著終端里進(jìn)入當(dāng)前文件夾, 使用 mock start芜抒,開啟本地服務(wù)器即可珍策。

- CORS(Cross-origin resource sharing) 跨域

CORS 也叫跨域資源共享,它是W3C標(biāo)準(zhǔn)宅倒,是跨源AJAX請(qǐng)求的根本解決方法,克服了AJAX只能同源使用的限制屯耸。相比JSONP只能發(fā)GET請(qǐng)求拐迁,CORS允許任何類型的請(qǐng)求,可以說(shuō)是老式JSONP的現(xiàn)代升級(jí)版疗绣。

目前线召,除了 IE瀏覽器IE10以下外,所有瀏覽器都支持該功能多矮。

對(duì)于開發(fā)者來(lái)說(shuō)缓淹,CORS通信與同源的AJAX通信沒有差別哈打,實(shí)現(xiàn)CORS通信的關(guān)鍵在與服務(wù)器支持與否,只要服務(wù)器實(shí)現(xiàn)了CORS接口讯壶,就可以跨域通信料仗。

還是上一個(gè)實(shí)例,服務(wù)器端開啟CORS的方法是伏蚊,在響應(yīng)頭信息中添加 Access-Control-Allow-Origin:

 res.header('Access-Control-Allow-Origin', 'http://wangpeng.com:8080')
 //res.header('Access-Control-Allow-Origin', '*')  

第二個(gè)參數(shù)用來(lái)聲明哪些源站有權(quán)限訪問(wèn)哪些資源立轧。
星號(hào) *代表來(lái)自任意域名的請(qǐng)求,都不會(huì)受到瀏覽器同源策略限制躏吊。

- 降域?qū)崿F(xiàn)跨域

和上面都是請(qǐng)求資源的場(chǎng)景不同氛改。對(duì)于兩個(gè)不同頁(yè)面的腳本,只有當(dāng)執(zhí)行它們的頁(yè)面位于具有相同的協(xié)議比伏,端口號(hào)胜卤,以及主機(jī)(document.domain 也就是原始域名----origin domain 設(shè)置為相同的值,也就是同時(shí)降域成一致)時(shí)赁项,這兩個(gè)腳本才能相互通信葛躏。

降域主要場(chǎng)景是, a.xxx.com 和 b.xxx.com 之間的訪問(wèn)肤舞,雖然都是xxx.com 子域名紫新,但是也存在瀏覽器同源策略的限制, 也無(wú)法直接獲取對(duì)方信息李剖。此時(shí)芒率,使用降域即可解決。

方法是: a.xxx.com 和 b.xxx.com 的頁(yè)面JS中篙顺, 同時(shí)加入 document.domain = "xxx.com" 彼此都降域到主域名偶芍。

于是二者可以在各自的頁(yè)面中,使用<iframe>引入另一個(gè)域名下同時(shí)做了降域的頁(yè)面德玫,并可以進(jìn)行相互操作匪蟀。

但是,如果不是一個(gè)主域名下的兩個(gè)二級(jí)域名宰僧,那么是不可能降域到一樣的材彪, 比如 a.baidu.com 和 b.taobao.com, 降域后分別是 baidu.com 和 taobao.com ,這顯然不是同源。

降域a頁(yè)面
降域b頁(yè)面

使用server-mock工具 或者h(yuǎn)ttp-server 搭建本地服務(wù)琴儿,任意打開a頁(yè)面段化,兩個(gè)input中任意一個(gè)輸入value值,另一個(gè)input會(huì)隨之改變造成,說(shuō)明實(shí)現(xiàn)了跨域操作显熏。

- postMessage

上例中通過(guò)降域,向其他窗口比如 iframe 晒屎、執(zhí)行window.open返回的窗口對(duì)象等發(fā)送數(shù)據(jù)喘蟆,實(shí)現(xiàn)兩個(gè)不同頁(yè)面的腳本跨域通信缓升,是存在局限性的,因?yàn)榻涤蛞惨从蛎麠l件蕴轨。

HTML5為了解決這個(gè)問(wèn)題港谊,引入了一個(gè)全新的API:跨文檔通信 API(Cross-document messaging)。
這個(gè)API為window對(duì)象新增了一個(gè)window.postMessage方法尺棋,允許跨窗口通信封锉,不論這兩個(gè)窗口是否同源。
舉例來(lái)說(shuō)膘螟,父窗口http://aaa.com向子窗口http://bbb.com發(fā)消息成福,調(diào)用postMessage方法就可以了。
對(duì)于不同的域下荆残,可以向其發(fā)送數(shù)據(jù)奴艾,如果對(duì)方認(rèn)可接受這個(gè)數(shù)據(jù),那么就可以使用内斯,如果對(duì)方?jīng)]有監(jiān)聽接受這個(gè)數(shù)據(jù)蕴潦,那么就沒有任何效果。

postMessage方法的第一個(gè)參數(shù)是具體的信息內(nèi)容俘闯,第二個(gè)參數(shù)是接收消息的窗口的源(origin)潭苞,即"協(xié)議 + 域名 + 端口"。也可以設(shè)為*真朗,表示不限制域名此疹,向所有窗口發(fā)送。

還是上一個(gè)應(yīng)用了降域的例子遮婶,這一次我們換用postMessage方法蝗碎。

a頁(yè)面:

    //發(fā)送
    document.querySelector('.main input').addEventListener('input', function(){
      console.log(this.value)
      //把輸入框的值發(fā)給兒子iframe,第二個(gè)參數(shù)指定哪些窗口能接收到消息事件旗扑,其值可以是字符串"*"(表示無(wú)限制)或者一個(gè)URI
      window.frames[0].postMessage(this.value, '*') 
    })
    //監(jiān)聽iframe的消息
    window.addEventListener('message', function(e){ 
      document.querySelector('.main input').value = e.data
      console.log(e.data)
    })
    //關(guān)于postMessage 的使用蹦骑,MDN文檔有詳細(xì)描述,已經(jīng)更規(guī)范更安全的建議臀防,本文只是做跨域的簡(jiǎn)單探討眠菇,簡(jiǎn)化了很多細(xì)節(jié)。

b頁(yè)面:

    //發(fā)送
    document.querySelector('#input').addEventListener('input', function(){
      window.parent.postMessage(this.value, '*') //把輸入框的值發(fā)給parent
    })
    //監(jiān)聽parent的消息
    window.addEventListener('message', function(e){
      document.querySelector('#input').value = e.data
      console.log(e.data)
    })

關(guān)于跨域問(wèn)題袱衷,MDN文檔有詳細(xì)使用描述琼锋,以及更規(guī)范更安全的建議,
另外祟昭,阮老師的這兩篇博文也做了詳盡的闡述。

阮一峰-瀏覽器同源政策及其規(guī)避方法
阮一峰-跨域資源共享 CORS 詳解

P.S. 最近學(xué)習(xí)了AJAX和跨域怖侦,剛好試著調(diào)用下API篡悟,發(fā)現(xiàn)很好玩! 可以做很多有趣的事情谜叹,唯一的局限是自身水平和想象力的局限。就在剛剛寫這篇拙文的時(shí)候搬葬,就發(fā)現(xiàn)放在github上的音樂(lè)頁(yè)面荷腊,不能請(qǐng)求到資源,說(shuō)我請(qǐng)求的mixed content(混合內(nèi)容急凰,確切說(shuō)是音頻資源) 被block掉了女仰。google一下,才恍然想起來(lái)抡锈,github pages 是 https協(xié)議的疾忍。趕緊跑去換了個(gè)https協(xié)議的API,搞定床三。

音樂(lè)API調(diào)用演示


如有任何想法或疑惑一罩,歡迎評(píng)論區(qū)提出,我們一起探討:D

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末撇簿,一起剝皮案震驚了整個(gè)濱河市聂渊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌四瘫,老刑警劉巖汉嗽,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異找蜜,居然都是意外死亡饼暑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門锹杈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撵孤,“玉大人,你說(shuō)我怎么就攤上這事竭望⌒奥耄” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵咬清,是天一觀的道長(zhǎng)闭专。 經(jīng)常有香客問(wèn)我,道長(zhǎng)旧烧,這世上最難降的妖魔是什么影钉? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮掘剪,結(jié)果婚禮上平委,老公的妹妹穿的比我還像新娘。我一直安慰自己夺谁,他們只是感情好廉赔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布肉微。 她就那樣靜靜地躺著,像睡著了一般蜡塌。 火紅的嫁衣襯著肌膚如雪碉纳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天馏艾,我揣著相機(jī)與錄音劳曹,去河邊找鬼。 笑死琅摩,一個(gè)胖子當(dāng)著我的面吹牛铁孵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迫吐,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼库菲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了志膀?” 一聲冷哼從身側(cè)響起熙宇,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溉浙,沒想到半個(gè)月后烫止,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡戳稽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年馆蠕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惊奇。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡互躬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颂郎,到底是詐尸還是另有隱情吼渡,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布乓序,位于F島的核電站寺酪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏替劈。R本人自食惡果不足惜寄雀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陨献。 院中可真熱鬧盒犹,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至脖阵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間墅茉,已是汗流浹背命黔。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留就斤,地道東北人悍募。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像洋机,于是被迫代替她去往敵國(guó)和親坠宴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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

  • 題目1.什么是同源策略? 同源策略(Same origin Policy): 瀏覽器出于安全方面的考慮绷旗,只允許與本...
    FLYSASA閱讀 1,721評(píng)論 0 6
  • 1.什么是同源策略瀏覽器出于安全方面的考慮喜鼓,只允許與本域下的接口交互。不同源的客戶端腳本在沒有明確授權(quán)的情況下衔肢,不...
    24_Magic閱讀 496評(píng)論 0 0
  • 1. 跨域和同源 首先來(lái)看摘自MDN上對(duì)于跨域庄岖,較為標(biāo)準(zhǔn)的解釋: 當(dāng)一個(gè)資源從與該資源本身所在的服務(wù)器不同的域或端...
    曉風(fēng)殘?jiān)?994閱讀 422評(píng)論 0 0
  • 1. 什么是同源策略 瀏覽器限制不同源的兩個(gè)網(wǎng)站間腳本和文本的相互訪問(wèn),只允許訪問(wèn)同源下的內(nèi)容角骤。所謂同源隅忿,就是指兩...
    熊蛋子17閱讀 686評(píng)論 1 6
  • 久聞西界彩云深, 綠葉紅花醉美人邦尊。 澧水遠(yuǎn)來(lái)惆悵去背桐, 芳心難許隘層層。 丙申五月十七
    東林梁閱讀 213評(píng)論 2 31