一文让你读懂JSONP跨域原理

2,688 阅读6分钟

一文读懂JSONP跨域原理

前言

相信很多小伙伴们都知道什么是跨域,为了照顾不清楚的小伙伴,这边先简单介绍下,跨域是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。

一、为什么会出现JSONP

出于浏览器安全限制,数据是不可以直接跨域(包括不同的根域名、二级域名、或不同的端口)请求的,除非目标域名授权你可以访问。所以我们采用另外一种方式来实现跨域请求,小伙伴们还记得可以让浏览器发起跨域请求的标签有哪些吗:

  1. iframe 标签
  2. img 标签
  3. link 标签
  4. script 标签
  5. ...... 不知道的小伙伴需要去复习下咯,接下来,我们就来介绍下这和JSONP有什么关系呢?

二、什么是JSONP

JSONP 是一种动态 script 标签跨域请求技术。指的是请求方动态创建 script 标签,src指向响应方的服务器,同时传一个参数callbackcallback 后面是我们的 functionName,当请求方向响应方发起请求时,响应方根据传过来的参数callback,构造并调用形如:xxx.call(undefined,'你要的数据'),其中'你要的数据'的传入格式是以JSON格式传入的,因为传入的JSON数据具有左右 padding,因而得名 JSONP 。后端代码构造并调用了xxx,浏览器接收到了响应,就会执行xxx.call(undefined,'你要的数据'),于是乎,请求方就知道了他要的数据了。

三、JSONP如何使用

现在我们来基于 node  来做一个 JSONP 跨域请求 服务端:

const Koa = require('koa');
const bodyParser = require("koa-bodyparser");
const { getUser } = require("./mock");
const app = new Koa();

app.use(bodyParser());


app.use(async (ctx, next) => {

  const { path: curPath } = ctx.request;

  if (curPath === '/jsonp') {
    // 设置响应头
    ctx.set('Content-Type', 'application/javascript;charset=utf-8');
    ctx.set('X-Content-Type-Options', 'nosniff');
    const callback = ctx.query.callback;
    let data =  getUser(ctx.query.type);
    data = JSON.stringify(data);
    ctx.body = `${callback}(${data})`
    console.log(ctx.query)
  }

});

console.log("服务器已启动!")
app.listen(3030);

客户端:

const btnHTMLElement = document.getElementById('btn');

btn.addEventListener('click',() => {
    let url = 'http://127.0.0.1:3030/jsonp?type=all&callback=getdata';
    loadScripr(url);
},false)

function loadScripr(src: string): void {
    const scriptHTMLScriptElement = document.createElement('script');
    script.src = src;
    script.onload = () => {
      // 每次动态创建script标签之后,都将script标签删掉 这很重要哦,不然整个页面的 script 标签要爆炸了
        document.body.removeChild(script)
    }
    script.onerror = () => {
        console.error('请求失败了');
       delete window['getdata'];
        document.body.removeChild(script)
    }
    document.body.appendChild(script);
}

function getdata(data: any): void {// data 为服务端返回的数据
   // to do something
    alert(JSON.stringify(data))
}

  

ps: 上面的代码没有做兼容性处理,在低级浏览器使用时需要做一下处理哦

当我们发送请求后将会显示: jsonP1.png 类似这样的结构:

<script>
// data 为后端返回的数据
getdata(data)
</script>

那为什么会出现这这样的情况呢? 回答这个问题之前,我们先来回顾下在 html 中我们是如何引入外部 js 库文件的,假设当我们引入 JQuery 库时,我们可以在响应头中看到有这么个字段 Content-Type:application/javascript; 如下图红色圈起来的部分,正因为有这个字段浏览器会根据请求头的类型对应做响应的解析操作,在这里就会执行里面的代码,然后我们的全局对象中就有了 JQuery 了。 jsonP2.png jsonP3.png 细心的小伙伴们估计都看到了,在我们在服务端请求时我们设置了请求头为,Content-Type :application/javascript;charset=utf-8 ,因此当浏览器解析资源时,会根据响应头做对应的操作,就有了如下的形式了~

<script>
// data 为后端返回的数据
getdata(data)
</script>

因为函数调用是在全局作用域的,所以我们的接收数据的回调函数要 全局定义在客户端 这很重要哦。

四、JSONP优缺点

既然 JSONP 这么好用那他有什么优缺点呢,让我们来看看吧~
优点

  1. 它不像 XMLHttpRequest 对象实现的Ajax请求那样受到同源策略的限制,JSONP可以跨越同源策略;
  2. 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequestActiveX的支持
  3. 在请求完毕后可以通过调用callback的方式回传结果。将回调方法的权限给了调用方。这个就相当于将controller层和view层终于分开了。我提供的jsonp服务只提供纯服务的数据,至于提供服务以 后的页面渲染和后续view操作都由调用者来自己定义就好了。如果有两个页面需要渲染同一份数据,你们只需要有不同的渲染逻辑就可以了,逻辑都可以使用同 一个jsonp服务。

缺点

  1. 它只支持GET请求而不支持POST等其它类型的HTTP请求(jsonp是通过 script 的 src 实现的,因为浏览器以为是请求一个资源,所以用的是GET,而不是其他)
  2. 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
  3. jsonp在调用失败的时候不会返回各种HTTP状态码。
  4. 安全性。万一假如提供jsonp的服务存在页面注入漏洞,即它返回的javascript的内容被人控制的。那么结果是什么?所有调用这个 jsonp的网站都会存在漏洞。于是无法把危险控制在一个域名下…所以在使用jsonp的时候必须要保证使用的jsonp服务必须是安全可信的。
  5. script 标签的 onerror 函数在 HTML5 才定义,并且即使我们定义了 onerror 处理函数,我们也不容易捕捉到错误发生的原因