JSONP 原理详解
JSONP (JSON with Padding) 是一种绕过浏览器同源策略限制的跨域数据请求技术,它巧妙地利用了 HTML <script>
标签的跨域特性实现数据获取。
关键机制
- 利用
<script>
标签的跨域能力:
- 浏览器允许跨域加载 JS 文件
- 不受同源策略限制
- 动态回调函数:
- 客户端定义全局回调函数
- 服务器将数据"包裹"在该函数调用中返回
- 即时执行:
- 返回的 JS 代码到达客户端后立即执行
- 自动触发预定义的回调函数
实现步骤详解
1. 客户端实现
<script>
// 步骤1:定义全局回调函数
function handleUserData(data) {
console.log('收到用户数据:', data);
document.getElementById('result').innerHTML =
`用户: ${data.name}, 年龄: ${data.age}`;
}
// 步骤2:动态创建 script 标签
function fetchData() {
const script = document.createElement('script');
// 设置带回调函数名的请求URL
script.src = 'https://api.example.com/user?callback=handleUserData';
// 步骤3:添加到DOM触发请求
document.body.appendChild(script);
}
</script>
<button onclick="fetchData()">获取用户数据</button>
<div id="result"></div>
2. 服务器端响应
// Node.js 示例
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
const { query } = url.parse(req.url, true);
const callback = query.callback;
if (req.url.startsWith('/user')) {
// 模拟用户数据
const userData = JSON.stringify({
name: "张三",
age: 28,
email: "zhangsan@example.com"
});
// 关键步骤:返回JS函数调用
res.writeHead(200, {'Content-Type': 'application/javascript'});
res.end(`${callback}(${userData})`);
}
});
server.listen(3000);
服务器响应内容
// 实际返回给客户端的内容
handleUserData({
"name": "张三",
"age": 28,
"email": "zhangsan@example.com"
})
JSONP 工作流程
- 客户端初始化:
- 定义全局回调函数(如
handleUserData
) - 创建
<script>
标签并设置src
属性 - URL 中包含回调函数名参数(
?callback=handleUserData
)
- 发起请求:
- 浏览器发现
<script>
标签 - 向目标服务器发起 GET 请求
- 服务器响应:
- 服务器解析 URL 获取回调函数名
- 准备 JSON 数据
- 将数据包裹在回调函数调用中返回
- 客户端处理:
- 浏览器下载并执行返回的 JS 代码
- 自动调用预定义的回调函数
- 在回调函数中处理数据
安全机制
主要安全措施
- 白名单验证(服务器端):
// 只允许特定的回调函数名
const allowedCallbacks = ['handleUserData', 'getProductInfo'];
if (!allowedCallbacks.includes(callback)) {
return res.status(400).end('Invalid callback');
}
- 内容类型验证:
// 强制设置为 JavaScript 类型
res.setHeader('Content-Type', 'application/javascript');
- 输入过滤:
// 防止XSS
const sanitizedData = JSON.stringify(data)
.replace(/</g, '\\u003c'); // 转义尖括号
安全风险
- XSS :
- 恶意服务器可能返回危险代码
- 防护:只信任已知安全源
- CSRF 风险:
- 自动携带用户 Cookie
- 防护:使用 CSRF token
- 数据更改:
- 中间人可能修改响应
- 防护:使用 HTTPS
与现代技术的对比
特性 | JSONP | CORS | WebSocket |
协议 | HTTP(S) | HTTP(S) | WS(S) |
请求类型 | GET 仅 | 所有方法 | 双向通信 |
数据格式 | JSONP | 任意 | 二进制/文本 |
安全性 | 较低 | 高 | 高 |
浏览器支持 | 所有浏览器 | IE10+ | 所有现代浏览器 |
复杂度 | 简单 | 中等(需服务器配置) | 高 |
实际应用场景
1. 跨域数据获取
// 获取天气数据
function getWeather(city) {
const script = document.createElement('script');
script.src = `https://weather-api.com/data?city=${city}&callback=showWeather`;
document.body.appendChild(script);
}
function showWeather(data) {
console.log(`温度: ${data.temp}℃, 湿度: ${data.humidity}%`);
}
2. 第三方登录
// 使用社交媒体登录
function authWithProvider(provider) {
const popup = window.open(
`https://${provider}.com/auth?callback=handleAuth`,
'_blank'
);
}
function handleAuth(userData) {
localStorage.setItem('user', JSON.stringify(userData));
}
JSONP 的局限性
- 仅支持 GET 请求:
- 无法发送 POST/PUT/DELETE 请求
- URL 长度限制(约 2000 字符)
- 错误处理困难:
// 超时处理示例
function fetchWithTimeout() {
const TIMEOUT = 5000;
let timedOut = false;
setTimeout(() => {
if (!timedOut) {
timedOut = true;
console.error('请求超时');
// 清理资源
}
}, TIMEOUT);
// 正常JSONP请求...
}
- 安全性限制:
- 无法设置自定义请求头
- 无法进行身份验证
- 回调函数管理:
- 多个并发请求需要不同回调
let counter = 0;
function jsonpRequest(url, callback) {
const funcName = `jsonpCallback_${Date.now()}_${counter++}`;
// 动态创建回调
window[funcName] = (data) => {
callback(data);
delete window[funcName]; // 清理
document.body.removeChild(script);
};
const script = document.createElement('script');
script.src = `${url}?callback=${funcName}`;
document.body.appendChild(script);
}
现代替代方案
1. CORS (推荐)
// 客户端
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
// 服务端设置
Access-Control-Allow-Origin: https://yourdomain.com
Access-Control-Allow-Methods: GET, POST, PUT
2. 代理服务器
客户端 → 同源代理服务器 → 目标API
3. WebSockets
const socket = new WebSocket('wss://api.example.com');
socket.onmessage = (event) => {
console.log('收到数据:', JSON.parse(event.data));
};
总结
JSONP 的核心价值在于:
- 绕过同源策略:利用
<script>
标签的跨域特性 - 简单易实现:无需复杂配置
- 广泛兼容性:支持所有浏览器
尽管在现代开发中已被 CORS 取代,但理解 JSONP 的机制对于:
- 维护老系统
- 理解浏览器安全模型
- 处理特殊场景
仍然具有重要意义。在实际应用中,优先考虑更安全的 CORS 方案,JSONP 仅作为兼容老浏览器的备选方案。