跳到主要内容

JSONP 跨域方案

一、认识


JSONP 利用 <script> 标签不受同源策略限制的特性。客户端通过动态创建 <script> 标签向跨域地址发起请求,服务器返回一段 JavaScript 代码(通常是一个调用预先约定的回调函数的代码),该代码在客户端执行后把数据传递给指定的回调函数。JSONP 跨域方案 只能发送get请求,不支持postputdelete , 安全性较低,因为返回的内容是可执行代码,容易受到 XSS 攻击。

二、实现


2.1 前端

前端: 定义callback函数,通过<script></script> 发送请求

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实现 JSONP</title>
</head>
<body>
<script>
function callback(result){
console.log("JSONP 回调函数",result);
}
</script>
<script src="http://localhost:3000/jsonp?name=abc&age=23&callback=callback"></script>
</body>
</html>

2.2 服务端

服务端: 服务端返回响应类型为text/javascript类型的callback()即可

const Koa = require("koa");
const Router = require("koa-router");

const app = new Koa();
const router = new Router({ prefix: "/" });

router.get("jsonp", async (ctx) => {
const { callback,...params } = ctx.request.query;
const { name, age} = params;
console.log("传入的参数为:",name,age);
const data = {
code: 200,
msg: "JSONP 请求成功!",
data: {},
};
ctx.type = "text/javascript";
ctx.body = `${callback}(${JSON.stringify(data)})`;
});

app.use(router.routes());
app.listen(3000, () => {
console.log("jsonp 服务启动成功!");
});

三、封装


3.1 前端

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>实现 JSONP</title>
</head>
<body>
<script>
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
const generateURL = () => {
let paramsStr = "";
for (let key in params) {
paramsStr += `${key}=${params[key]}&`;
}
paramsStr += `callback=${callback}`;
return `${url}?${paramsStr}`;
};
callback =
callback ||
`callback${Math.floor(Math.random() * (10 - 0 + 1)) + 0}`;
const script = document.createElement("script");
script.src = generateURL();
document.body.append(script);
window[callback] = function (data) {
resolve(data);
delete window[callback];
document.body.remove(script);
};
});
}

jsonp({
url: "http://localhost:3000/jsonp",
params: {
name: "abc",
age: 23,
},
}).then((result) => {
console.log(result);
});
</script>
</body>
</html>

3.2 服务端

const Koa = require("koa");
const Router = require("koa-router");

const app = new Koa();
const router = new Router({ prefix: "/" });

router.get("jsonp", async (ctx) => {
const { callback,...params } = ctx.request.query;
const { name, age} = params;
console.log("传入的参数为:",name,age);
const data = {
code: 200,
msg: "JSONP 请求成功!",
data: {},
};
ctx.type = "text/javascript";
ctx.body = `${callback}(${JSON.stringify(data)})`;
});

app.use(router.routes());
app.listen(3000, () => {
console.log("jsonp 服务启动成功!");
});

四、问题


4.1 JsonP 跨域中, 服务端执行 Callback 之后, 前端是如何找到 Callback 并执行的?

JSONP 实际上利用了浏览器允许跨域加载脚本这一特性。前端在发起 JSONP 请求时,会动态创建一个 <script> 标签,并在请求 URL 中传递一个回调函数名(例如 callback=myFunction), 而这个回调函数是事先定义在全局作用域中的(通常挂在 window 对象上)。当服务端接收到这个请求后, 会把数据包装成 JavaScript 代码, 例如 myFunction({...data...})。浏览器在加载这个返回的脚本时, 会立即执行其中的代码, 由于全局中已经存在 myFunction, 它就会被调用, 并接收数据作为参数。这就是前端 找到 并执行回调的过程。