JSONP 跨域方案
一、认识
JSONP
利用 <script>
标签不受同源策略限制的特性。客户端通过动态创建 <script>
标签向跨域地址发起请求,服务器返回一段 JavaScript
代码(通常是一个调用预先约定的回调函数的代码),该代码在客户端执行后把数据传递给指定的回调函数。JSONP
跨域方案 只能发送get
请求,不支持post
、put
、delete
, 安全性较低,因为返回的内容是可执行代码,容易受到 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
, 它就会被调用, 并接收数据作为参数。这就是前端 找到 并执行回调的过程。