JSONP

JSONP или «JSON with padding» (JSON с набивкой) — это дополнение к базовому формату JSON. Он предоставляет способ запросить данные с сервера, находящегося в другом домене — операцию, запрещённую в типичных веб-браузерах из-за политики ограничения домена.

В июле 2005 года Джордж Джемпти (George Jempty) предложил возможность предварять JSON необязательным объявлением переменной.[1][2] Исходное предложение JSONP, где набивкой является функция обратного вызова, скорее всего было сделано Бобом Ипполито (Bob Ippolito) в декабре 2005 года[3] и используется ныне многими Web 2.0-приложениями, такими как Dojo Toolkit, Google Web Toolkit,[4] и веб-службами.

Описание методики

[править | править код]

Согласно политике ограничения домена, веб-страница, расположенная на сервере example1.com, не может связаться с сервером, отличным от него самого (например, с example2.com). В основу технологии JSONP положен тот факт, что политика безопасности браузера не запрещает использовать HTML-элемент <script type="text/javascript" src="…"/> для обращения к серверам, отличным от сервера, с которого произошла загрузка страницы. Используя открытую политику для элементов <script>, некоторые страницы используют их, чтобы загружать JavaScript-код, оперирующий динамически создаваемыми JSON-данными из других источников. Запросы для JSONP получают не JSON, а произвольный JavaScript-код. Они обрабатываются интерпретатором JavaScript, а не парсером JSON. Существуют серьезные риски, связанные с безопасностью при использовании JSONP, в большинстве ситуаций использование CORS является лучшим выбором.

Схему работы паттерна можно описать, используя запрос по некоему URL, возвращающий JSON-данные. Программа на JavaScript могла бы запросить этот URL, к примеру, посредством XMLHttpRequest. Предположим, UserId объекта Foo равен 1234. Браузер, запрашивающий URL http://server2.example.com/Users/1234, передав Id равный 1234, получит ответ следующего формата:

{
    "Name": "Foo",
    "Id": 1234,
    "Rank": 7
}

JSON-данные в ответе стороннего сервера обычно создаются динамически, в зависимости от параметров запроса, переданных в URL.

Ниже HTML-элемент <script> указывает в качестве атрибута src ссылку, возвращающую JSON:

<script type="application/javascript" src="http://server2.example.com/Users/1234">
    
</script>

В свою очередь, браузер скачает файл script, разберёт его содержимое, интерпретирует сырые JSON-данные как блок и выкинет ошибку синтаксиса. Даже если данные были интерпретированы как литеральный объект JavaScript, к нему невозможно получить доступ из JavaScript, выполняемого в браузере, поскольку без присвоения переменной объектные литералы недоступны.

В паттерне JSONP URL, на который указывает атрибут src тега <script>, возвращает данные JSON, обёрнутые в вызов функции. В подобном случае функция, уже определённая в среде JavaScript, может манипулировать JSON-данными. Начинка JSONP может выглядеть так:

   functionCall({"Name": "Foo", "Id": 1234, "Rank": 7});

Вызов функции — это и есть «P» в слове JSONP — «padding» (набивка, «отступ») вокруг чистого JSON, или, согласно некоторым источникам[5], — «префикс». По соглашению, браузер передаёт имя функции обратного вызова как именованный параметр запроса, обычно используя имя jsonp или callback в запросе к серверу, то есть,

 <script type="text/javascript" src="http://server2.example.com/Users/1234?jsonp=parseResponse">
    
 </script>

В данном примере начинка будет такова:

   parseResponse({"Name": "Foo", "Id": 1234, "Rank": 7});

В то время как набивка (префикс) является обычно именем функции обратного вызова, определённой внутри контекста выполнения в браузере. Кроме имени функции префикс может означать имя переменной, оператор if, или любой другой оператор JavaScript. Ответ на JSONP-запрос (строго говоря — запрос, соответствующий паттерну JSONP) не является объектом JSON и не расценивается браузером, как таковой. «Начинка» может быть любым выражением на JavaScript, и вовсе не требует, чтобы внутри обязательно был JSON. Но обычно это фрагмент JavaScript, применяющий вызов функции к неким JSON-данным.

Другими словами, типичное применение JSONP предоставляет междоменный доступ к существующему JSON API путём оборачивания начинки JSON в вызов функции.

Инъекция элемента script

[править | править код]

JSONP имеет смысл, только когда используется с элементом script. Для каждого нового JSONP-запроса браузер должен добавить новый элемент <script> или использовать существующий. Первая манипуляция — добавление нового элемента script — осуществляется через динамическое манипулирование DOM, и известна как инъекция элемента script. Элемент <script> вставляется в HTML DOM, с URL желаемой конечной точки JSONP в атрибуте «src».

Эта динамическая инъекция элемента script обычно выполняется вспомогательной библиотекой javascript. У jQuery и других фреймворков имеются вспомогательные функции для JSONP; существуют также и отдельные решения[6][7].

Динамически вставляемый элемент script для вызовов JSONP выглядит следующим образом:

 <script type="text/javascript" src="http://server2.example.com/Users/1234?jsonp=parseResponse">
    
 </script>

После вставки элемента, браузер обрабатывает его и выполняет HTTP GET для src URL, получая содержимое. Затем браузер обрабатывает возвращённую полезную нагрузку как JavaScript. Обычно это выполнение функции.

В этом смысле применение JSONP можно охарактеризовать как разрешить браузерным страницам обойти политику ограничения домена путём вставки элемента script.

Соображения безопасности

[править | править код]

Включение тегов script с других серверов позволяет удалённым серверам подмешать в веб-сайт любое содержимое. Если у удалённых серверов есть уязвимости, допускающие подмешивание JavaScript, страница, предоставляемая оригинальным сервером, подвергается повышенному риску.

В настоящее время предпринимаются шаги, чтобы определить более безопасное строгое подмножество JSON-P[8], которое браузеры могли бы принудительно включать при запросах скрипта со специфичным MIME-типом, как например «application/json-p». Если ответ не парсится как строгий JSON-P, браузер мог бы выводить ошибку или просто игнорировать весь ответ. Однако, на текущий момент единственным правильным MIME-типом для JSONP является «application/javascript»[9].

Подделка межсайтовых запросов

[править | править код]

Примитивные размещения JSONP подвержены опасности подделки межсайтовых запросов (CSRF или XSRF)[10]. Поскольку HTML-тег <script> не подчиняется политике ограничения домена в реальных реализациях браузеров, вредоносная страница может запросить и получить JSON-данные, принадлежащие другому сайту. Это позволит данным в формате JSON быть обработанными в контексте вредоносной страницы, возможно, раскрывая пароли или другие секретные данные, если пользователь залогинен на другом сайте.

Это вызывает проблемы только если данные, закодированные в JSON, содержат конфиденциальную информацию, которую нельзя разглашать третьей стороне, и сервер полагается на политику ограничения домена браузера для блокировки передачи данных в случае неправильного запроса. Проблема не существует, если сервер самостоятельно определяет уместность запроса, передавая данные только если запрос правильный. Cookies сами по себе не являются адекватным способом определения правомерности запроса. Использование одних лишь только cookies подвержено подделке межсайтовых запросов.

JSONPP (англ. parameterized JSON with padding — «параметризованный JSON с подкладкой») — развитие идеи JSONP.

JSONPP включает в себя URL источника, имя функции, которая будет обрабатывать JSON данные, строка для eval после получения данных и строка для eval после окончания обработки данных:

JSON_call(SRC, JSONP, JSONPP, ONLOAD);

в итоге оборачивается

ans = JSONP(SRC)
{
    eval(JSONPP(ans));
    eval(ONLOAD);
}

Вообще, для самой идеи JSONPP не принципиально количество параметров. Достаточно SRC, JSONP, JSONPP (и их обработка на стороне сервера, а затем клиента) для того, чтобы это был JSONPP. Рассмотрим на примере работы с сервисом S3DB.

function s3db_jsonpp_call(src, next_eval){
	var call = "call_"+Math.random().toString().replace(/\./g,"");
	var headID = document.getElementsByTagName("head")[0];
	var script = document.createElement('script');
	script.id = call;
	script.type = 'text/javascript';
	// using padded, parameterized json
	src = src+"&format=json&jsonp=s3db_jsonpp&jsonpp="+next_eval+"&onload=remove_element_by_id('"+script.id+"')";
	script.src = src;
	headID.appendChild(script); // retrieve answer
}
function s3db_jsonpp(ans, jsonpp){
	eval(jsonpp);
	return ans;
}
function remove_element_by_id(id){
	var e = document.getElementById(id);
	e.parentNode.removeChild(e);
	return false;
}

В примере функция s3db_jsonpp_call() создаёт в DOM в части head элемент script, src которого соответствует вызову JSONPP.

После получения ответа от сервера будет вызвана s3db_jsonpp() — она передана в параметрах вызова, как это должно быть по правилам JSONP.

Внутри s3db_jsonpp() сработает eval(jsonpp), и произойдёт возврат значения ans.

Вызов eval(onload) приводит к выполнению remove_element_by_id() с id созданного скрипта в head и в итоге к его удалению, ведь он уже всё равно не будет использоваться, поскольку id в примере было сгенерировано случайным образом в самом начале функции s3db_jsonpp_call(). Этот вызов в ответе сервера.

Примечания

[править | править код]
  1. eval'ing JSON (19 июля 2005). Архивировано из оригинала 12 февраля 2006 года.
  2. json: Message: Re: Comments (17 августа 2005). Архивировано из оригинала 17 января 2013 года.
  3. Remote JSON - JSONP. from __future__ import *. Bob.pythonmac.org (5 декабря 2005). Дата обращения: 8 сентября 2008. Архивировано из оригинала 17 января 2013 года.
  4. GWT Tutorial: How to Read Web Services Client-Side with JSONP. Google Web Toolkit Applications (6 февраля 2008). Дата обращения: 3 июля 2009. Архивировано из оригинала 17 января 2013 года.
  5. Experimental RDF result set to JSON translator. Дата обращения: 20 февраля 2012. Архивировано из оригинала 17 января 2013 года.
  6. example jsonp library on pastebin. Архивировано из оригинала 17 января 2013 года.
  7. jQuery's $.getJSON utility. Архивировано из оригинала 17 января 2013 года.
  8. Safer cross-domain Ajax with JSON-P/JSONP. JSON-P.org. Дата обращения: 30 октября 2011. Архивировано из оригинала 17 января 2013 года.
  9. Grey, Eli Is this safe for providing JSONP? stackoverflow.com (27 июня 2010). Дата обращения: 7 сентября 2012. Архивировано из оригинала 13 февраля 2013 года.
  10. Grossman, Jeremiah Advanced Web Attack Techniques using GMail (27 января 2006). Дата обращения: 3 июля 2009. Архивировано из оригинала 17 января 2013 года.