美文网首页JavaScript前端学习JavaScript
浏览器跨域问题小体会-使用原生js跨域访问豆瓣api

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

作者: lanzhiheng | 来源:发表于2017-03-22 16:51 被阅读1997次

    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 !!!

    相关文章

      网友评论

      • solfKwolf:谢谢大大分享,感激不尽
        lanzhiheng:@LiuZJ_OTG 哈
      • a2d5f8877582:script.setAttribute('src': 'https://api.douban.com/v2/book/user/119280372/collections'); // 设置script标签的src属性为对应的url

        你这代码报错,是因为把` ,` 写成了 `:` 啊,,,😶
      • 32f569ef606a:大神,你写得太好了,像我这样一年不评论几次的渣渣都忍不住留下对您的赞美之意,您写得简洁风趣易懂,有大神之风范!小弟收益匪浅
        但是目前还不能解决小弟的问题,
        小弟我遇到的问题刚刚相反,请求的url -》 http://www.fs121.com/pub/1/fost.js 好像返回的不是json,是一个js文件呢?还是一个数组呢?
        好像不是数组,
        因为我用 http://www.fs121.com/pub/1/fost.js?callback=handleResponse 这种方式不能把返回的数据放在函数中,
        所以我觉得这是一个js文件!!问题来啦,怎么提取返回的js文件里的数据呢。。。
        lanzhiheng:@小3子 差不多睡了。你只需要问清楚客户这个接口是否支持跨域?有些支持跨域的接口如cdn是可以直接在浏览器请求的。不需要通过jsonp这么麻烦 关键还是问清楚服务端是否支持:mask:
        32f569ef606a:@lanzhiheng 哇,想不到大神居然这么晚还不睡觉,还回复了我!现在的问题是我没有权限修改服务端的代码,客户又坚持我司一定要用这api的地址,郁闷:sob:
        lanzhiheng:@小3子 那有可能是服务端不支持跨域 或者是回调函数不一定是用callback这个关键字来指定的 具体用什么关键字还得看文档 或者服务端api的源代码
      • luax:很好的思路,这样服务端就不用再封装jsonp的支持。收下了
        lanzhiheng:@luax 多谢支持
      • llbbove:刚好遇到这个问题
        lanzhiheng:@llbbove thx
        llbbove:@hengrjgc :smile: 已关注,感谢楼主分享
        lanzhiheng:@llbbove 哈哈

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

      本文链接:https://www.haomeiwen.com/subject/tduonttx.html