Что такое JSONP (JSON с заполнением), для чего он был создан и когда его следует использовать? Я понимаю JSON, но запутался с JSONP. Технические объяснения, которые я нашёл, упоминают “внедрение тега script” и “префикс как входной аргумент”, но эти концепции мне не ясны. Может кто-нибудь объяснить JSONP простыми словами и объяснить, какую проблему он решает?
JSONP (JSON with Padding) — это техника, которая позволяет веб-браузерам получать данные с разных доменов, оборачивая JSON-данные в вызов функции, тем самым эффективно обходя ограничения политики одного источника (same-origin policy). Она была создана как раннее решение для кросс-доменного обмена данными до того, как CORS (Cross-Origin Resource Sharing) получил широкую поддержку. Вы должны использовать JSONP в основном при работе с устаревшими API, которые не поддерживают CORS, или когда необходимо поддерживать очень старые браузеры, хотя он в значительной степени был заменен современными реализациями CORS.
Содержание
- Что такое JSONP?
- Проблема, которую решает JSONP: Политика одного источника
- Как работает JSONP: Объяснение процесса
- Объяснение внедрения тега script
- Функция обратного вызова и заполнение (padding)
- Когда использовать JSONP в современной разработке
- Вопросы безопасности при использовании JSONP
- JSONP против CORS: Сравнение
Что такое JSONP?
JSONP расшифровывается как “JSON with Padding” (JSON с заполнением). По своей сути JSONP — это не другой формат по сравнению с JSON, это просто JSON-данные, обернутые в вызов функции. “Заполнение” (padding) относится к этой функции-обертке, которая позволяет данным выполняться как код JavaScript, а не анализироваться как статические данные.
Например, обычный JSON-ответ может выглядеть так:
{
"name": "John",
"age": 30,
"city": "New York"
}
А JSONP-ответ будет выглядеть так:
callbackFunction({
"name": "John",
"age": 30,
"city": "New York"
});
Эта простая техника позволила реализовать мощный кросс-доменный обмен данными в ранние дни веб-разработки.
Проблема, которую решает JSONP: Политика одного источника
Чтобы понять, почему JSONP был необходим, нам нужно понять политику одного источника (same-origin policy). Это фундаментальная функция безопасности в веб-браузерах, которая предотвращает доступ скриптов на одной странице к данным с другого домена с другим источником (разный протокол, домен или порт).
Например, если ваш веб-сайт находится по адресу https://example.com, он не может напрямую делать AJAX-запросы к https://api.anotherdomain.com из-за ограничений политики одного источника. Это было серьезным ограничением для веб-приложений, которым требовалось потреблять данные из нескольких источников.
JSONP предоставил элегантный способ обойти это, используя лазейку в модели безопасности браузера: хотя браузеры ограничивают XMLHttpRequest (основу для AJAX) при выполнении кросс-доменных запросов, они не ограничивают загрузку скриптов с других доменов.
Как работает JSONP: Объяснение процесса
Процесс JSONP включает несколько этапов, которые работают вместе для обеспечения кросс-доменного получения данных:
-
Запрос клиента: Вместо стандартного AJAX-запроса клиент создает тег
<script>с источником, указывающим на конечную точку API, включая параметр обратного вызова:html<script src="https://api.example.com/data?callback=processData"></script> -
Ответ сервера: Сервер получает этот запрос и распознает параметр обратного вызова. Вместо того чтобы возвращать обычный JSON, он оборачивает JSON-данные в вызов функции, используя предоставленное имя обратного вызова:
javascriptprocessData({ "data": "example data" }); -
Выполнение в браузере: Когда браузер загружает этот скрипт, JavaScript интерпретирует его как выполняемый код, а не как статические данные. Функция
processDataопределена на странице клиента и выполняется с данными в качестве аргумента. -
Обработка данных: Функция обратного вызова клиента обрабатывает полученные данные и может обновить веб-страницу или выполнить другие операции.
Этот процесс эффективно “обманывает” браузер, заставляя его выполнять кросс-доменные данные как код JavaScript, обходя ограничения политики одного источника.
Объяснение внедрения тега script
Концепция “внедрения тега script” (script tag injection) является фундаментальной для того, как работает JSONP. Вот что это означает простыми словами:
Обычно, когда вы включаете скрипты на веб-страницу, вы добавляете их в свой HTML следующим образом:
<script src="https://example.com/script.js"></script>
При использовании JSONP вместо включения существующего файла скрипта вы динамически создаете тег script, который указывает на конечную точку API. Это “внедряет” тег script в среду выполнения вашей веб-страницы. Вот как это работает программно:
function JSONPRequest(url, callbackName) {
// Создаем уникальное имя функции обратного вызова
const functionName = callbackName + '_' + Date.now();
// Создаем функцию обратного вызова
window[functionName] = function(data) {
// Обрабатываем данные
console.log('Полученные данные:', data);
// Очищаем функцию
delete window[functionName];
};
// Создаем элемент script
const script = document.createElement('script');
script.src = url + '?callback=' + functionName;
script.onerror = function() {
// Обрабатываем ошибку
delete window[functionName];
};
// Добавляем скрипт в документ
document.body.appendChild(script);
}
// Используем функцию
JSONPRequest('https://api.example.com/data', 'process');
Эта техника внедрения тега script и делает JSONP возможной — она использует способность браузера загружать и выполнять скрипты с любого домена.
Функция обратного вызова и заполнение (padding)
“Заполнение” (padding) в JSONP относится к функции-обертке, которая содержит JSON-данные. Это служит двум важным целям:
-
Контекст выполнения: Заполнение предоставляет контекст функции для выполнения JSON-данных, позволяя обрабатываться JavaScript, а не рассматриваться как статическое содержимое.
-
Механизм доставки: Функция обратного вызова действует как “механизм доставки” — это функция, которую клиент сообщает серверу использовать при оборачивании JSON-ответа.
Вот как работает процесс обратного вызова:
На стороне клиента:
// Определяем функцию обратного вызова
function handleData(response) {
console.log('Получено:', response);
// Обрабатываем данные и обновляем интерфейс
document.getElementById('result').textContent = response.name;
}
// Запрашиваем данные с параметром обратного вызова
var script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleData';
document.body.appendChild(script);
Ответ сервера:
// Сервер получает запрос и отвечает:
handleData({
"name": "API Response",
"timestamp": "2023-01-01T00:00:00Z"
});
Заполнение (вызов функции handleData) и делает JSON-данные выполняемыми. Без него браузер просто отобразил бы JSON как текст, а не выполнял бы его как код JavaScript.
Когда использовать JSONP в современной разработке
Хотя JSONP когда-то был важной техникой, его актуальность снизилась с появлением CORS. Вот конкретные сценарии, где вам все еще может понадобиться использовать JSONP:
-
Поддержка устаревших API: Некоторые старые API поддерживают только JSONP и не были обновлены для поддержки CORS. Если вам нужно интегрироваться с этими сервисами, JSONP может быть вашим единственным вариантом.
-
Совместимость с браузерами: JSONP работает в очень старых браузерах, которые не поддерживают CORS (до Internet Explorer 10). Если вам нужно поддерживать древние браузеры, JSONP может быть необходим.
-
Простые кросс-доменные запросы: Для простых запросов только на чтение данных, где вам не нужны POST-запросы, JSONP можно реализовать straightforwardly.
Однако есть важные ограничения, которые следует учитывать:
- Только GET-запросы: JSONP может делать только GET-запросы, а не POST, PUT, DELETE или другие методы HTTP.
- Нет обработки ошибок: В отличие от AJAX-запросов, JSONP не имеет стандартных механизмов обработки ошибок.
- Риски безопасности: JSONP может подвергать ваше приложение уязвимостям, таким как JSONP hijacking.
Вот практический пример, когда вы можете использовать JSONP:
// Для API погоды, который поддерживает только JSONP
function getWeatherData() {
const callbackName = 'weatherCallback_' + Date.now();
// Создаем временную функцию обратного вызова
window[callbackName] = function(data) {
console.log('Данные о погоде:', data);
// Отображаем информацию о погоде
displayWeather(data);
// Очищаем
delete window[callbackName];
};
// Создаем и внедряем тег script
const script = document.createElement('script');
script.src = `https://weather-api.example.com/forecast?location=NewYork&callback=${callbackName}`;
script.onerror = function() {
console.error('Не удалось загрузить данные о погоде');
delete window[callbackName];
};
document.head.appendChild(script);
}
function displayWeather(data) {
const weatherElement = document.getElementById('weather');
weatherElement.innerHTML = `
<h3>${data.location}</h3>
<p>Температура: ${data.temperature}°F</p>
<p>Условия: ${data.conditions}</p>
`;
}
Вопросы безопасности при использовании JSONP
JSONP introduces several security risks that developers should be aware of:
-
JSONP Hijacking: This is the most significant security concern with JSONP. Since JSONP responses execute JavaScript code, malicious actors can create pages that steal data from JSONP endpoints. If a JSONP endpoint returns sensitive user data, attackers can create a page that includes the JSONP URL with their own callback function, effectively stealing the data.
-
No Server-Side Validation: JSONP requests don’t include standard CORS headers, so you can’t easily implement server-side validation of cross-origin requests.
-
Callback Injection: If not properly sanitized, attackers could potentially inject malicious code through the callback parameter.
To mitigate these risks:
- Never expose sensitive data through JSONP endpoints
- Validate and sanitize callback function names
- Implement proper authentication and authorization
- Consider using CORS instead whenever possible
Here’s an example of secure JSONP implementation on the server side:
// Node.js example of secure JSONP endpoint
const express = require('express');
const app = express();
app.get('/api/data', (req, res) => {
// Validate callback name (allow only alphanumeric)
const callback = req.query.callback;
if (!/^[a-zA-Z0-9_]+$/.test(callback)) {
return res.status(400).send('Invalid callback function name');
}
// Get data (with proper authentication)
const data = {
publicData: "This is safe to share",
timestamp: new Date().toISOString()
};
// Return JSONP response
res.send(`${callback}(${JSON.stringify(data)});`);
});
app.listen(3000);
JSONP против CORS: Сравнение
Хотя JSONP был стандартным решением для кросс-доменных запросов до 2010 года, CORS (Cross-Origin Resource Sharing) в значительной степени его заменил. Вот как они сравниваются:
JSONP:
- Метод: Поддерживает только GET-запросы
- Формат данных: Требует JSON, обернутый в вызов функции
- Поддержка браузеров: Работает в очень старых браузерах
- Обработка ошибок: Нет стандартной обработки ошибок
- Безопасность: Более высокий риск кражи данных
- Сложность: Требует специальной реализации на стороне сервера
CORS:
- Метод: Поддерживает все методы HTTP (GET, POST, PUT, DELETE и т.д.)
- Формат данных: Стандартный JSON, XML или любой другой формат
- Поддержка браузеров: Современные браузеры (IE10+, все остальные)
- Обработка ошибок: Стандартные коды статуса HTTP и обработка ошибок
- Безопасность: Более безопасен при правильной настройке заголовков
- Сложность: Прощая реализация с правильными заголовками сервера
Вот сравнительная таблица:
| Характеристика | JSONP | CORS |
|---|---|---|
| Методы запросов | Только GET | Все методы HTTP |
| Формат данных | JSON, обернутый в вызов функции | Любой формат |
| Поддержка браузеров | Очень старые браузеры | Современные браузеры (IE10+) |
| Обработка ошибок | Ограниченная | Стандартные HTTP-ошибки |
| Безопасность | Уязвим для hijacking | Более безопасен при правильных заголовках |
| Реализация | Сложная на стороне сервера | Простые HTTP-заголовки |
| Производительность | Немного медленнее из-за загрузки скрипта | Более эффективный |
Современная веб-разработка должна предпочитать CORS JSONP, где это возможно. Вот почему:
// Современный пример CORS (гораздо проще, чем JSONP)
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
console.log('Полученные данные:', data);
})
.catch(error => {
console.error('Ошибка:', error);
});
Подход с CORS чище, безопаснее и поддерживает все методы HTTP и правильную обработку ошибок.
Источники
- MDN Web Docs - JSONP
- W3C CORS Specification
- Stack Overflow - What is JSONP and why was it created?
- SitePoint - JSONP: What It Is and How to Use It
- OWASP JSONP Security Considerations
Заключение
JSONP был умным обходным решением для ограничения политики одного источника, которое беспокоило раннюю веб-разработку. Оборачивая JSON-данные в вызов функции и используя внедрение тега script, JSONP позволил реализовать кросс-доменный обмен данными до того, как CORS получил широкую поддержку.
Ключевые выводы:
- JSONP — это JSON, обернутый в вызов функции, который выполняется как JavaScript
- Он был создан для обхода политики одного источника при кросс-доменных запросах
- Внедрение тега script — это механизм, который позволяет JSONP работать
- Функция обратного вызова действует как “заполнение” (padding), которое оборачивает JSON-данные
- JSONP следует использовать только при необходимости, так как CORS — это современная, более безопасная альтернатива
- Всегда учитывайте последствия для безопасности при реализации JSONP
Для современной веб-разработки предпочитайте CORS JSONP, где это возможно. Используйте JSONP только в ситуациях, когда вам нужно поддерживать устаревшие API или очень старые браузеры, которые не поддерживают CORS.