浏览器跨域问题小体会-使用原生js跨域访问豆瓣api

2017年第一篇博客,从去年11月份开始到现在已经几个月没有动笔了。写这篇博客的契机是,有个哥们在自己的博客系统上想加载个人在豆瓣上的读书信息。而且

1. 不想使用ruby来做这个事情(后端请求),因为他觉得拿到信息后放到后台处理,最后再丢到前端去渲染的方式很挫。
2. 如果在浏览器端使用js来fetch对应数据的话会产生跨域问题。

OK,容我从下面几个方面来写写近期对跨域问题的理解

1. 怎样才算跨域?
2. 我们要如何发送跨域请求?
3. 如何跨域调用豆瓣的api来获取对应的图书数据?

一. 怎样才算跨域

跨域,其实简单地去理解就是不同域名之间的http请求。比如我朋友的豆瓣api是 https://api.douban.com/v2/book/user/119280372/collections,我需要从浏览器通过js来获取这个url返回的数据,我使用比较传统的做法是用XMLHttpRequest创建一个对象来做这个事情。

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

好像很合理,但是如果我是在百度的首页做这个事情,浏览器会给我这个反馈

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.

表明这是一个跨域请求。犀牛书里面有个比较简单的判断请求是否跨域的方法就是同时判断需要访问的url的域名以及端口号,如果 (域名不同 || 端口号不同) 为逻辑true, 则表示对这个url的请求是一个跨域请求。看上面的例子,首先域名就不一样了,马上就能够判断这是一个跨域请求了。

二. 如何发送跨域请求

这个时候我们会有疑问,我们<img>这个标签不是可以获取到其他域名下的图片数据吗?非常正确。我们可以给<img>标签设置src属性为https://unsplash.it/1000/150

来获取这个图片服务网站上面的图片数据,从程序员的角度去理解的话,它发送了一个GET请求,但是它的局限性就在于,它就只能发送这个GET请求了,而且我们似乎不能做更多的事情了。

下面介绍两种方式

1. XMLHttpRequest

有些浏览器本身就支持XMLHttpRequest所产生对象的跨域请求,我们还可以自定义需要的请求方法(GET, POST), 一般情况下是通过判断该对象是否具有withCredentials这个属性来判断的。

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

这样就表示我这个浏览器的XMLHttpRequest所产生的对象是支持跨域请求的。但是问题来了,为什么刚才我用它来请求豆瓣的api的时候还会有跨域的错?

因为跨域请求还需要服务端支持,产生跨域请求的时候还是要选择值得信赖的合作伙伴

举个离我们最近的例子。有些CDN网络它本身服务就是支持跨域请求的。我们可以做个实验。我们使用https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css这个url来请求css资源。当然我们要发送的是跨域请求:

Paste_Image.png

这里表示这个请求已经成功了,并且没有产生跨域问题,因为这个服务本身支持跨域请求的,具体怎么支持,这个要服务端的小伙伴自己摸索了。

2. JSONP

我们还是没有解决豆瓣上的跨域问题。之前同学解决的方式是使用下面代码

$.ajax({
       type : "get", //jquey是不支持post方式跨域的
       async: false,
       url : "https://api.douban.com/v2/book/user/119280372/collections", //跨域请求的URL
       dataType : "jsonp",
       //传递给请求处理程序,用以获得jsonp回调函数名的参数名(默认为:callback)
       jsonp: "callback",
       //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
       jsonpCallback:"success_jsonpCallback",
       //成功获取跨域服务器上的json数据后,会动态执行这个callback函数
       success : function(json){
         
      }
    });

但是我表示我完全不知道它在说什么,jsonp是什么?直到最近看犀牛书刚好碰到这个跨域相关的内容。才稍微有点理解。

JSONP是用<script>元素作为Ajax传输的技术。

咋一看,跟<img>标签是不是有点儿像,他们都是可以通过设置src属性对应的url来从服务端获取数据,而且他们本身是允许跨域的。但是他们却只能单向地从服务端获取数据。这就可以理解了为什么上述的jquery代码里面写着jsonp只支持跨域GET请求了吧(<script>标签其实是无法发送POST这类修改服务端数据的请求的)。然后,我们再回顾一下<script>标签是怎么工作的:

1. 从URL获取对应的js脚本。
2. 浏览器运行对应脚本。

这样看来似乎我们就可以通过,把对应的豆瓣url放入到script标签的src属性里面。然后,添加这个标签到文档里面,它就会自动请求url并且获取对应的数据。

不过这里也是有问题的:

我们获取服务端返回的数据之后,浏览器会把对应的数据当作js执行,万一我们获取的数据不是js脚本,怎么办? 我们看下面的例子:

var script = document.createElement('script');  //  创建一个script标签
script.setAttribute('src': 'https://api.douban.com/v2/book/user/119280372/collections'); // 设置script标签的src属性为对应的url
document.body.appendChild(script);  // 把script标签插入到body元素的最后,插入之后就会直接发起跨域请求。 

我在浏览器端运行上述脚本,但是很不幸:

Paste_Image.png

原因上面也说了,其实豆瓣返回的是json数据,我们script标签加载之后把它当作js来运行了,所以报错是可以理解的。

接下来咋办, 当服务端返回的是json数据的时候,如果我们能够把json数据放进函数里面进行处理就好了。

当我们获得的数据是 {name: "lanzhiheng"} 的时候,我多么希望我能够通过:

callback({name: "lanzhiheng"})

来对返回的数据进行处理啊!!!

我们其实可以告诉服务器让它给我们返回一个JSONP响应,而不单单是JSON数据,常用的方式是在url后面添加一个?jsonp=callback这样的查询字符串,形如

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

然后我们返回的数据就会有个名为callback的函数包裹着。当然想得美 ! 这个东西既然是服务端支持,当然不会写死。除了jsonp还可能会有其他的参数名字,对于豆瓣而言,它是用callback来作为参数名的,为了避免混淆我把url改成下面这样

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

尝试在浏览器调用这个方法。

Paste_Image.png

Awesome,我们返回的json数据已经被一个handleResponse函数包裹着,我们只需要提前对handleResponse进行定义,就可以对这堆json数据为所欲为了。回顾上面的jquery代码,也就不难理解jsonp参数名对应的值为何是callback了吧?

三. 如何跨域调用豆瓣的api来获取对应的图书数据

其实在介绍jsonp的时候已经基本上把核心代码都写出来了,最为核心的代码其实就是

var script = document.createElement('script');  //  创建一个script标签
script.setAttribute('src': 'https://api.douban.com/v2/book/user/119280372/collections'); // 设置script标签的src属性为对应的url
document.body.appendChild(script);  // 把script标签插入到body元素的最后,插入之后就会直接发起跨域请求。 

我们可以稍微把代码写得好一点,定义一个函数给他自动追加查询字符串。(犀牛书里面的对应例子精简版本)

var responseHandler; // 定义一个全局作用域的函数

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

  // 创建script 标签
  var script = document.createElement('script');


  // 在函数内部实现包裹函数,因为要用到cb
  responseHandler = function(json) {
    try {
      cb(json)
    } finally {
      // 函数调用之后不管发生什么都要移除对应的标签,留着也没用
      script.parentNode.removeChild(script);
    }
  }

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

OK,现在测试一下这个函数,我们需要传入一个对返回数据进行处理的函数。我这里简单地把数据打印出来

Paste_Image.png

很好, 结果就是我们需要的,我们已经可以把跨域请求所返回的JSON数据打印出来了,当然后续是否需要进行更多的操作这就取决于你了!

很感谢您能看完 _

Happy Coding and Writing !!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容