一文读懂JSONP跨域原理
前言
相信很多小伙伴们都知道什么是跨域,为了照顾不清楚的小伙伴,这边先简单介绍下,跨域是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
一、为什么会出现JSONP
出于浏览器安全限制,数据是不可以直接跨域(包括不同的根域名、二级域名、或不同的端口)请求的,除非目标域名授权你可以访问。所以我们采用另外一种方式来实现跨域请求,小伙伴们还记得可以让浏览器发起跨域请求的标签有哪些吗:
iframe
标签img
标签link
标签script
标签- ......
不知道的小伙伴需要去复习下咯,接下来,我们就来介绍下这和
JSONP
有什么关系呢?
二、什么是JSONP
JSONP 是一种动态 script
标签跨域请求技术。指的是请求方动态创建 script
标签,src
指向响应方的服务器,同时传一个参数callback
,
callback
后面是我们的 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 btn: HTMLElement = 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 script: HTMLScriptElement = 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: 上面的代码没有做兼容性处理,在低级浏览器使用时需要做一下处理哦
当我们发送请求后将会显示: 类似这样的结构:
<script>
// data 为后端返回的数据
getdata(data)
</script>
那为什么会出现这这样的情况呢? 回答这个问题之前,我们先来回顾下在 html
中我们是如何引入外部 js
库文件的,假设当我们引入 JQuery
库时,我们可以在响应头中看到有这么个字段 Content-Type
:application/javascript;
如下图红色圈起来的部分,正因为有这个字段浏览器会根据请求头的类型对应做响应的解析操作,在这里就会执行里面的代码,然后我们的全局对象中就有了 JQuery
了。
细心的小伙伴们估计都看到了,在我们在服务端请求时我们设置了请求头为,Content-Type
:application/javascript;charset=utf-8
,因此当浏览器解析资源时,会根据响应头做对应的操作,就有了如下的形式了~
<script>
// data 为后端返回的数据
getdata(data)
</script>
因为函数调用是在全局作用域的,所以我们的接收数据的回调函数要 「全局定义在客户端」 这很重要哦。
四、JSONP优缺点
既然 JSONP
这么好用那他有什么优缺点呢,让我们来看看吧~
「优点」
- 它不像
XMLHttpRequest
对象实现的Ajax
请求那样受到同源策略的限制,JSONP
可以跨越同源策略; - 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要
XMLHttpRequest
或ActiveX
的支持 - 在请求完毕后可以通过调用callback的方式回传结果。将回调方法的权限给了调用方。这个就相当于将
controller
层和view
层终于分开了。我提供的jsonp服务只提供纯服务的数据,至于提供服务以 后的页面渲染和后续view操作都由调用者来自己定义就好了。如果有两个页面需要渲染同一份数据,你们只需要有不同的渲染逻辑就可以了,逻辑都可以使用同 一个jsonp服务。
「缺点」
- 它只支持GET请求而不支持POST等其它类型的HTTP请求(jsonp是通过 script 的 src 实现的,因为浏览器以为是请求一个资源,所以用的是GET,而不是其他)
- 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
- jsonp在调用失败的时候不会返回各种HTTP状态码。
- 安全性。万一假如提供jsonp的服务存在页面注入漏洞,即它返回的javascript的内容被人控制的。那么结果是什么?所有调用这个 jsonp的网站都会存在漏洞。于是无法把危险控制在一个域名下…所以在使用jsonp的时候必须要保证使用的jsonp服务必须是安全可信的。
- script 标签的 onerror 函数在 HTML5 才定义,并且即使我们定义了 onerror 处理函数,我们也不容易捕捉到错误发生的原因