瀏覽器跨域問題小體會(huì)-使用原生js跨域訪問豆瓣api

2017年第一篇博客,從去年11月份開始到現(xiàn)在已經(jīng)幾個(gè)月沒有動(dòng)筆了威根。寫這篇博客的契機(jī)是瓶竭,有個(gè)哥們?cè)谧约旱牟┛拖到y(tǒng)上想加載個(gè)人在豆瓣上的讀書信息。而且

1. 不想使用ruby來做這個(gè)事情(后端請(qǐng)求)挠他,因?yàn)樗X得拿到信息后放到后臺(tái)處理,最后再丟到前端去渲染的方式很挫扳抽。
2. 如果在瀏覽器端使用js來fetch對(duì)應(yīng)數(shù)據(jù)的話會(huì)產(chǎn)生跨域問題。

OK殖侵,容我從下面幾個(gè)方面來寫寫近期對(duì)跨域問題的理解

1. 怎樣才算跨域?
2. 我們要如何發(fā)送跨域請(qǐng)求?
3. 如何跨域調(diào)用豆瓣的api來獲取對(duì)應(yīng)的圖書數(shù)據(jù)?

一. 怎樣才算跨域

跨域贸呢,其實(shí)簡(jiǎn)單地去理解就是不同域名之間的http請(qǐng)求。比如我朋友的豆瓣api是 https://api.douban.com/v2/book/user/119280372/collections拢军,我需要從瀏覽器通過js來獲取這個(gè)url返回的數(shù)據(jù)楞陷,我使用比較傳統(tǒng)的做法是用XMLHttpRequest創(chuàng)建一個(gè)對(duì)象來做這個(gè)事情。

var req = new XMLHttpRequest()
req.open('GET', 'https://api.douban.com/v2/book/user/119280372/collections')
req.send(null)

好像很合理茉唉,但是如果我是在百度的首頁做這個(gè)事情固蛾,瀏覽器會(huì)給我這個(gè)反饋

XMLHttpRequest cannot load https://api.douban.com/v2/book/user/119280372/collections. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://www.baidu.com' is therefore not allowed access.

表明這是一個(gè)跨域請(qǐng)求结执。犀牛書里面有個(gè)比較簡(jiǎn)單的判斷請(qǐng)求是否跨域的方法就是同時(shí)判斷需要訪問的url的域名以及端口號(hào),如果 (域名不同 || 端口號(hào)不同) 為邏輯true, 則表示對(duì)這個(gè)url的請(qǐng)求是一個(gè)跨域請(qǐng)求艾凯∠揍#看上面的例子,首先域名就不一樣了趾诗,馬上就能夠判斷這是一個(gè)跨域請(qǐng)求了蜡感。

二. 如何發(fā)送跨域請(qǐng)求

這個(gè)時(shí)候我們會(huì)有疑問,我們<img>這個(gè)標(biāo)簽不是可以獲取到其他域名下的圖片數(shù)據(jù)嗎恃泪?非常正確郑兴。我們可以給<img>標(biāo)簽設(shè)置src屬性為https://unsplash.it/1000/150

來獲取這個(gè)圖片服務(wù)網(wǎng)站上面的圖片數(shù)據(jù),從程序員的角度去理解的話贝乎,它發(fā)送了一個(gè)GET請(qǐng)求杈笔,但是它的局限性就在于,它就只能發(fā)送這個(gè)GET請(qǐng)求了糕非,而且我們似乎不能做更多的事情了。

下面介紹兩種方式

1. XMLHttpRequest

有些瀏覽器本身就支持XMLHttpRequest所產(chǎn)生對(duì)象的跨域請(qǐng)求球榆,我們還可以自定義需要的請(qǐng)求方法(GET, POST)朽肥, 一般情況下是通過判斷該對(duì)象是否具有withCredentials這個(gè)屬性來判斷的。

var supprotCORS = (new XMLHttpRequest()).withCredentials !== undefined
>> undefined
supprotCORS
>> true

這樣就表示我這個(gè)瀏覽器的XMLHttpRequest所產(chǎn)生的對(duì)象是支持跨域請(qǐng)求的持钉。但是問題來了衡招,為什么剛才我用它來請(qǐng)求豆瓣的api的時(shí)候還會(huì)有跨域的錯(cuò)?

因?yàn)榭缬蛘?qǐng)求還需要服務(wù)端支持,產(chǎn)生跨域請(qǐng)求的時(shí)候還是要選擇值得信賴的合作伙伴每强。

舉個(gè)離我們最近的例子始腾。有些CDN網(wǎng)絡(luò)它本身服務(wù)就是支持跨域請(qǐng)求的。我們可以做個(gè)實(shí)驗(yàn)空执。我們使用https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css這個(gè)url來請(qǐng)求css資源浪箭。當(dāng)然我們要發(fā)送的是跨域請(qǐng)求:

Paste_Image.png

這里表示這個(gè)請(qǐng)求已經(jīng)成功了,并且沒有產(chǎn)生跨域問題辨绊,因?yàn)檫@個(gè)服務(wù)本身支持跨域請(qǐng)求的奶栖,具體怎么支持,這個(gè)要服務(wù)端的小伙伴自己摸索了门坷。

2. JSONP

我們還是沒有解決豆瓣上的跨域問題宣鄙。之前同學(xué)解決的方式是使用下面代碼

$.ajax({
       type : "get", //jquey是不支持post方式跨域的
       async: false,
       url : "https://api.douban.com/v2/book/user/119280372/collections", //跨域請(qǐng)求的URL
       dataType : "jsonp",
       //傳遞給請(qǐng)求處理程序,用以獲得jsonp回調(diào)函數(shù)名的參數(shù)名(默認(rèn)為:callback)
       jsonp: "callback",
       //自定義的jsonp回調(diào)函數(shù)名稱默蚌,默認(rèn)為jQuery自動(dòng)生成的隨機(jī)函數(shù)名
       jsonpCallback:"success_jsonpCallback",
       //成功獲取跨域服務(wù)器上的json數(shù)據(jù)后,會(huì)動(dòng)態(tài)執(zhí)行這個(gè)callback函數(shù)
       success : function(json){
         
      }
    });

但是我表示我完全不知道它在說什么冻晤,jsonp是什么?直到最近看犀牛書剛好碰到這個(gè)跨域相關(guān)的內(nèi)容绸吸。才稍微有點(diǎn)理解鼻弧。

JSONP是用<script>元素作為Ajax傳輸?shù)募夹g(shù)设江。

咋一看,跟<img>標(biāo)簽是不是有點(diǎn)兒像温数,他們都是可以通過設(shè)置src屬性對(duì)應(yīng)的url來從服務(wù)端獲取數(shù)據(jù)绣硝,而且他們本身是允許跨域的。但是他們卻只能單向地從服務(wù)端獲取數(shù)據(jù)撑刺。這就可以理解了為什么上述的jquery代碼里面寫著jsonp只支持跨域GET請(qǐng)求了吧(<script>標(biāo)簽其實(shí)是無法發(fā)送POST這類修改服務(wù)端數(shù)據(jù)的請(qǐng)求的)鹉胖。然后,我們?cè)倩仡櫼幌?code><script>標(biāo)簽是怎么工作的:

1. 從URL獲取對(duì)應(yīng)的js腳本。
2. 瀏覽器運(yùn)行對(duì)應(yīng)腳本够傍。

這樣看來似乎我們就可以通過甫菠,把對(duì)應(yīng)的豆瓣url放入到script標(biāo)簽的src屬性里面。然后冕屯,添加這個(gè)標(biāo)簽到文檔里面寂诱,它就會(huì)自動(dòng)請(qǐng)求url并且獲取對(duì)應(yīng)的數(shù)據(jù)。

不過這里也是有問題的:

我們獲取服務(wù)端返回的數(shù)據(jù)之后安聘,瀏覽器會(huì)把對(duì)應(yīng)的數(shù)據(jù)當(dāng)作js執(zhí)行痰洒,萬一我們獲取的數(shù)據(jù)不是js腳本,怎么辦? 我們看下面的例子:

var script = document.createElement('script');  //  創(chuàng)建一個(gè)script標(biāo)簽
script.setAttribute('src': 'https://api.douban.com/v2/book/user/119280372/collections'); // 設(shè)置script標(biāo)簽的src屬性為對(duì)應(yīng)的url
document.body.appendChild(script);  // 把script標(biāo)簽插入到body元素的最后浴韭,插入之后就會(huì)直接發(fā)起跨域請(qǐng)求丘喻。 

我在瀏覽器端運(yùn)行上述腳本,但是很不幸:

Paste_Image.png

原因上面也說了念颈,其實(shí)豆瓣返回的是json數(shù)據(jù)泉粉,我們script標(biāo)簽加載之后把它當(dāng)作js來運(yùn)行了,所以報(bào)錯(cuò)是可以理解的榴芳。

接下來咋辦, 當(dāng)服務(wù)端返回的是json數(shù)據(jù)的時(shí)候嗡靡,如果我們能夠把json數(shù)據(jù)放進(jìn)函數(shù)里面進(jìn)行處理就好了。

當(dāng)我們獲得的數(shù)據(jù)是 {name: "lanzhiheng"} 的時(shí)候窟感,我多么希望我能夠通過:

callback({name: "lanzhiheng"})

來對(duì)返回的數(shù)據(jù)進(jìn)行處理啊!!!

我們其實(shí)可以告訴服務(wù)器讓它給我們返回一個(gè)JSONP響應(yīng)讨彼,而不單單是JSON數(shù)據(jù),常用的方式是在url后面添加一個(gè)?jsonp=callback這樣的查詢字符串,形如

https://api.douban.com/v2/book/user/119280372/collections?jsonp=callback

然后我們返回的數(shù)據(jù)就會(huì)有個(gè)名為callback的函數(shù)包裹著柿祈。當(dāng)然想得美 ! 這個(gè)東西既然是服務(wù)端支持点骑,當(dāng)然不會(huì)寫死。除了jsonp還可能會(huì)有其他的參數(shù)名字谍夭,對(duì)于豆瓣而言黑滴,它是用callback來作為參數(shù)名的,為了避免混淆我把url改成下面這樣

https://api.douban.com/v2/book/user/119280372/collections?callback=handleResponse

嘗試在瀏覽器調(diào)用這個(gè)方法紧索。

Paste_Image.png

Awesome袁辈,我們返回的json數(shù)據(jù)已經(jīng)被一個(gè)handleResponse函數(shù)包裹著,我們只需要提前對(duì)handleResponse進(jìn)行定義珠漂,就可以對(duì)這堆json數(shù)據(jù)為所欲為了晚缩∥膊玻回顧上面的jquery代碼,也就不難理解jsonp參數(shù)名對(duì)應(yīng)的值為何是callback了吧?

三. 如何跨域調(diào)用豆瓣的api來獲取對(duì)應(yīng)的圖書數(shù)據(jù)

其實(shí)在介紹jsonp的時(shí)候已經(jīng)基本上把核心代碼都寫出來了荞彼,最為核心的代碼其實(shí)就是

var script = document.createElement('script');  //  創(chuàng)建一個(gè)script標(biāo)簽
script.setAttribute('src': 'https://api.douban.com/v2/book/user/119280372/collections'); // 設(shè)置script標(biāo)簽的src屬性為對(duì)應(yīng)的url
document.body.appendChild(script);  // 把script標(biāo)簽插入到body元素的最后冈敛,插入之后就會(huì)直接發(fā)起跨域請(qǐng)求。 

我們可以稍微把代碼寫得好一點(diǎn)鸣皂,定義一個(gè)函數(shù)給他自動(dòng)追加查詢字符串抓谴。(犀牛書里面的對(duì)應(yīng)例子精簡(jiǎn)版本)

var responseHandler; // 定義一個(gè)全局作用域的函數(shù)

function getJSONP(url, cb) {
  if (url.indexOf('?') === -1) {
    url += '?callback=responseHandler';
  } else {
    url += '&callback=responseHandler';
  }

  // 創(chuàng)建script 標(biāo)簽
  var script = document.createElement('script');


  // 在函數(shù)內(nèi)部實(shí)現(xiàn)包裹函數(shù),因?yàn)橐玫絚b
  responseHandler = function(json) {
    try {
      cb(json)
    } finally {
      // 函數(shù)調(diào)用之后不管發(fā)生什么都要移除對(duì)應(yīng)的標(biāo)簽寞缝,留著也沒用
      script.parentNode.removeChild(script);
    }
  }

  script.setAttribute('src', url)
  document.body.appendChild(script);
}

OK癌压,現(xiàn)在測(cè)試一下這個(gè)函數(shù),我們需要傳入一個(gè)對(duì)返回?cái)?shù)據(jù)進(jìn)行處理的函數(shù)荆陆。我這里簡(jiǎn)單地把數(shù)據(jù)打印出來

Paste_Image.png

很好, 結(jié)果就是我們需要的滩届,我們已經(jīng)可以把跨域請(qǐng)求所返回的JSON數(shù)據(jù)打印出來了,當(dāng)然后續(xù)是否需要進(jìn)行更多的操作這就取決于你了!

很感謝您能看完 _

Happy Coding and Writing !!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末被啼,一起剝皮案震驚了整個(gè)濱河市帜消,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浓体,老刑警劉巖泡挺,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異汹碱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)荞估,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門咳促,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人勘伺,你說我怎么就攤上這事跪腹。” “怎么了飞醉?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵冲茸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我缅帘,道長(zhǎng)轴术,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任钦无,我火速辦了婚禮逗栽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘失暂。我一直安慰自己彼宠,他們只是感情好鳄虱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凭峡,像睡著了一般拙已。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上摧冀,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天倍踪,我揣著相機(jī)與錄音,去河邊找鬼按价。 笑死惭适,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的楼镐。 我是一名探鬼主播癞志,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼框产!你這毒婦竟也來了凄杯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤秉宿,失蹤者是張志新(化名)和其女友劉穎戒突,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體描睦,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡膊存,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忱叭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隔崎。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡令蛉,死狀恐怖蕴纳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情守谓,我是刑警寧澤撵彻,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布钓株,位于F島的核電站,受9級(jí)特大地震影響陌僵,放射性物質(zhì)發(fā)生泄漏轴合。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一碗短、第九天 我趴在偏房一處隱蔽的房頂上張望值桩。 院中可真熱鬧,春花似錦豪椿、人聲如沸奔坟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咳秉。三九已至婉支,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間澜建,已是汗流浹背向挖。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炕舵,地道東北人何之。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像咽筋,于是被迫代替她去往敵國(guó)和親溶推。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理奸攻,服務(wù)發(fā)現(xiàn)蒜危,斷路器,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • http://blog.csdn.net/qq_34482827/article/details/51655914...
    cllian119閱讀 1,043評(píng)論 0 0
  • 跨域失敗 當(dāng)使用jsonp跨域時(shí)睹耐, 1:請(qǐng)求必須是GET 2:python 寫的webservice返回的格式是J...
    旅行家John閱讀 439評(píng)論 0 1
  • 0. 前言 說到AJAX就會(huì)不可避免的面臨兩個(gè)問題辐赞。 AJAX以何種格式來交換數(shù)據(jù)? 第二個(gè)是跨域的需求如何解決硝训?...
    公子七閱讀 23,604評(píng)論 7 67
  • 親愛的W跋煳!現(xiàn)在想到你窖梁,我依然心有波瀾赘风。 你...
    奕南笙閱讀 225評(píng)論 0 1