理解跨域异步请求的 JSON-P

在开篇之前,我们也许知道跨域问题的存在,知道通过服务端开放跨域请求来使API实现跨域访问,甚至也知道JSON-P这种处理方式,然而可能只是基于阅读的基础上,未必有去做过实践,于是JSON-P只是纸上谈兵,的确很多文章都太不“直接了当”。现在,我尝试着用自己的方式来阐述一下,力求做到简明扼要,同时也是对自己知识储备的整理。

跨域

跨域就客户端而言是这样理解的:客户端(浏览器)为安全起见,特意设置了资源请求的门槛——“同源策略”,即不允许在当前域下访问其他来源的资源,这个“域”或“源”在浏览器里表现为实际请求的URL。也就是说,浏览器不允许客户端在访问A站的同时请求B站的资源。域名不同,IP不同,甚至端口号不同都被视为跨域,除非服务端通过HTTP头标识告知客户端放开这一限制。我们的目的是要越过这道坎。

JSON-P

JSON-P是绕过客户端同源策略的方法之一,实现跨域请求是需要前后端技术协作的,是对跨域异步请求的其他方法的补充。它并非一门技术,而是一种技巧。上文说到浏览器设置了同源请求的门槛,然而这种限制通常只针对cookie、本地存储、Ajax、跨域DOM获取等,对静态资源的请求并没有限制,比如在某个网站插入一段来自CDN提供的Bootstrap库文件:


src指向的脚本可以被顺利的下载并执行。
  JSON-P将通过这一特性来绕过浏览器的同源限制,配合后端返回加工过的内容以实现跨域请求。

实现

“JSON-P”全名JSON with Padding,很形象的诠释了JSON的获取方式(JSON数据以填充的方式来获取),我们用一个经典的例子来说明:
  假设我们为 A站 后台设计一个API,允许第三方开发人员在 B站 通过这个API得到 A站 返回的形如JSON的数据{"name":"Warren","age":"28"}
  这个API其实是一个纯 js 的文本资源,包含一个带有形参的函数调用语句 getUserInfo(data);,把参数替换为用户信息数据,于是最终API将会返回如下文本:

getUserInfo({"name":"Warren","age":"28"});

同时确保该函数在 B站 客户端存在:


然后在 B站 页面上加载该资源:


浏览器“出于本能”的立即执行了该资源内的js语句,

当访问 B站 时就会依次弹出两个对话框。就这样,一个JSON-P请求完成了,它的确绕过了同源限制,实现在不同域间数据的交互。值得一提的是,客户端getUserInfo函数里得到的data实际上是JS的对象字面量(理论上可以是任何对象,这取决于服务端传递的内容),因为API返回的文本在浏览器上被当作JS代码解析并立即执行了,于是也就省去了JSON.parse()的步骤。

进阶

发现了没?上面的实现方式和平时在本地写JS几乎一样呀!只不过调用getUserInfo()函数的代码是通过后端书写并在客户端发送请求后返回的。那么问题来了,B站 开发人员希望借助这个API调取除用户信息之外更多的数据,比如天气、在 A站 的收藏……显然一个getUserInfo()是不够的,比较灵活的做法是 A站 后端依据 B站 客户端发出的需求来决定返回什么样的数据(可执行js文本),我们给调用API的链接添加查询参数,并把希望得到的数据类型告诉API:


或者


……

API根据查询参数getJSONP的值判断客户端需要什么样的数据,填充进对应的函数并返回给客户端执行。这就能让一个API满足多种跨域请求。相应地,客户端还是免不了要设计对应的数据处理函数的。

异步

原理已经掌握,我们可以来点更有趣的,比如异步请求,聪明的读者一定猜到了——不就是动态创建script标签或者改变已有标签的src属性值么?没错!封装一个用原生JS创建script标签的函数:

    function getData(src){
        var script = document.createElement('script');
        script.src = src;
        document.body.appendChild(script);
    }

在任何时候调用它:

getData('http://a.com/api.js?getJSONP=getUserInfo');

建议在数据成功获取后保存数据到一个新的变量,并移除空闲的script标签,最大限度的防治污染。

总结

JSON-P作为一种非标准的跨域请求实现有着先天的缺陷,它虽然巧妙,但实现起来繁琐怪异。一个现成的库来实现它再好不过了,比如JQuery的jsonp。然而仍难弥补只能接受GET请求和存在的安全隐患的缺点。未来JSON-P也许会被正式的跨域技术标准取代(比如CORS),但因为IE及其它老旧的浏览器的存在,新标准的超前使得JSON-P仍有存在的必要。

你可能感兴趣的:(理解跨域异步请求的 JSON-P)